Changeset 3621 in MondoRescue for branches/3.3/mindi-busybox/editors


Ignore:
Timestamp:
Dec 20, 2016, 4:07:32 PM (8 years ago)
Author:
Bruno Cornec
Message:

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

Location:
branches/3.3
Files:
10 edited
1 copied

Legend:

Unmodified
Added
Removed
  • branches/3.3/mindi-busybox/editors/Config.src

    r3232 r3621  
    77
    88INSERT
    9 
    10 config AWK
    11     bool "awk"
    12     default y
    13     help
    14       Awk is used as a pattern scanning and processing language. This is
    15       the BusyBox implementation of that programming language.
    16 
    17 config FEATURE_AWK_LIBM
    18     bool "Enable math functions (requires libm)"
    19     default y
    20     depends on AWK
    21     help
    22       Enable math functions of the Awk programming language.
    23       NOTE: This will require libm to be present for linking.
    24 
    25 config CMP
    26     bool "cmp"
    27     default y
    28     help
    29       cmp is used to compare two files and returns the result
    30       to standard output.
    31 
    32 config DIFF
    33     bool "diff"
    34     default y
    35     help
    36       diff compares two files or directories and outputs the
    37       differences between them in a form that can be given to
    38       the patch command.
    39 
    40 config FEATURE_DIFF_LONG_OPTIONS
    41     bool "Enable long options"
    42     default y
    43     depends on DIFF && LONG_OPTS
    44     help
    45       Enable use of long options.
    46 
    47 config FEATURE_DIFF_DIR
    48     bool "Enable directory support"
    49     default y
    50     depends on DIFF
    51     help
    52       This option enables support for directory and subdirectory
    53       comparison.
    54 
    55 config ED
    56     bool "ed"
    57     default y
    58     help
    59       The original 1970's Unix text editor, from the days of teletypes.
    60       Small, simple, evil. Part of SUSv3. If you're not already using
    61       this, you don't need it.
    62 
    63 config SED
    64     bool "sed"
    65     default y
    66     help
    67       sed is used to perform text transformations on a file
    68       or input from a pipeline.
    699
    7010config FEATURE_ALLOW_EXEC
  • branches/3.3/mindi-busybox/editors/Kbuild.src

    r3232 r3621  
    88
    99INSERT
    10 lib-$(CONFIG_AWK)       += awk.o
    11 lib-$(CONFIG_CMP)       += cmp.o
    12 lib-$(CONFIG_DIFF)      += diff.o
    13 lib-$(CONFIG_ED)        += ed.o
    14 lib-$(CONFIG_SED)       += sed.o
  • branches/3.3/mindi-busybox/editors/awk.c

    r3232 r3621  
    88 */
    99
     10//config:config AWK
     11//config:   bool "awk"
     12//config:   default y
     13//config:   help
     14//config:     Awk is used as a pattern scanning and processing language. This is
     15//config:     the BusyBox implementation of that programming language.
     16//config:
     17//config:config FEATURE_AWK_LIBM
     18//config:   bool "Enable math functions (requires libm)"
     19//config:   default y
     20//config:   depends on AWK
     21//config:   help
     22//config:     Enable math functions of the Awk programming language.
     23//config:     NOTE: This will require libm to be present for linking.
     24//config:
     25//config:config FEATURE_AWK_GNU_EXTENSIONS
     26//config:   bool "Enable a few GNU extensions"
     27//config:   default y
     28//config:   depends on AWK
     29//config:   help
     30//config:     Enable a few features from gawk:
     31//config:     * command line option -e AWK_PROGRAM
     32//config:     * simultaneous use of -f and -e on the command line.
     33//config:       This enables the use of awk library files.
     34//config:       Ex: awk -f mylib.awk -e '{print myfunction($1);}' ...
     35
     36//applet:IF_AWK(APPLET_NOEXEC(awk, awk, BB_DIR_USR_BIN, BB_SUID_DROP, awk))
     37
     38//kbuild:lib-$(CONFIG_AWK) += awk.o
     39
    1040//usage:#define awk_trivial_usage
    1141//usage:       "[OPTIONS] [AWK_PROGRAM] [FILE]..."
     
    1444//usage:     "\n    -F SEP      Use SEP as field separator"
    1545//usage:     "\n    -f FILE     Read program from FILE"
     46//usage:    IF_FEATURE_AWK_GNU_EXTENSIONS(
     47//usage:     "\n    -e AWK_PROGRAM"
     48//usage:    )
    1649
    1750#include "libbb.h"
     
    3972
    4073
     74#define OPTSTR_AWK \
     75    "F:v:f:" \
     76    IF_FEATURE_AWK_GNU_EXTENSIONS("e:") \
     77    "W:"
     78#define OPTCOMPLSTR_AWK \
     79    "v::f::" \
     80    IF_FEATURE_AWK_GNU_EXTENSIONS("e::")
     81enum {
     82    OPTBIT_F,   /* define field separator */
     83    OPTBIT_v,   /* define variable */
     84    OPTBIT_f,   /* pull in awk program from file */
     85    IF_FEATURE_AWK_GNU_EXTENSIONS(OPTBIT_e,) /* -e AWK_PROGRAM */
     86    OPTBIT_W,   /* -W ignored */
     87    OPT_F = 1 << OPTBIT_F,
     88    OPT_v = 1 << OPTBIT_v,
     89    OPT_f = 1 << OPTBIT_f,
     90    OPT_e = IF_FEATURE_AWK_GNU_EXTENSIONS((1 << OPTBIT_e)) + 0,
     91    OPT_W = 1 << OPTBIT_W
     92};
    4193
    4294#define MAXVARFMT       240
     
    156208/* simple token classes */
    157209/* Order and hex values are very important!!!  See next_token() */
    158 #define TC_SEQSTART 1           /* ( */
     210#define TC_SEQSTART (1 << 0)        /* ( */
    159211#define TC_SEQTERM  (1 << 1)        /* ) */
    160212#define TC_REGEXP   (1 << 2)        /* /.../ */
     
    176228#define TC_ELSE     (1 << 18)
    177229#define TC_BUILTIN  (1 << 19)
    178 #define TC_GETLINE  (1 << 20)
    179 #define TC_FUNCDECL (1 << 21)       /* `function' `func' */
    180 #define TC_BEGIN    (1 << 22)
    181 #define TC_END      (1 << 23)
    182 #define TC_EOF      (1 << 24)
    183 #define TC_VARIABLE (1 << 25)
    184 #define TC_ARRAY    (1 << 26)
    185 #define TC_FUNCTION (1 << 27)
    186 #define TC_STRING   (1 << 28)
    187 #define TC_NUMBER   (1 << 29)
     230/* This costs ~50 bytes of code.
     231 * A separate class to support deprecated "length" form. If we don't need that
     232 * (i.e. if we demand that only "length()" with () is valid), then TC_LENGTH
     233 * can be merged with TC_BUILTIN:
     234 */
     235#define TC_LENGTH   (1 << 20)
     236#define TC_GETLINE  (1 << 21)
     237#define TC_FUNCDECL (1 << 22)       /* `function' `func' */
     238#define TC_BEGIN    (1 << 23)
     239#define TC_END      (1 << 24)
     240#define TC_EOF      (1 << 25)
     241#define TC_VARIABLE (1 << 26)
     242#define TC_ARRAY    (1 << 27)
     243#define TC_FUNCTION (1 << 28)
     244#define TC_STRING   (1 << 29)
     245#define TC_NUMBER   (1 << 30)
    188246
    189247#define TC_UOPPRE  (TC_UOPPRE1 | TC_UOPPRE2)
     
    191249/* combined token classes */
    192250#define TC_BINOP   (TC_BINOPX | TC_COMMA | TC_PIPE | TC_IN)
    193 #define TC_UNARYOP (TC_UOPPRE | TC_UOPPOST)
     251//#define   TC_UNARYOP (TC_UOPPRE | TC_UOPPOST)
    194252#define TC_OPERAND (TC_VARIABLE | TC_ARRAY | TC_FUNCTION \
    195                    | TC_BUILTIN | TC_GETLINE | TC_SEQSTART | TC_STRING | TC_NUMBER)
     253                   | TC_BUILTIN | TC_LENGTH | TC_GETLINE \
     254                   | TC_SEQSTART | TC_STRING | TC_NUMBER)
    196255
    197256#define TC_STATEMNT (TC_STATX | TC_WHILE)
     
    199258
    200259/* word tokens, cannot mean something else if not expected */
    201 #define TC_WORD    (TC_IN | TC_STATEMNT | TC_ELSE | TC_BUILTIN \
    202                    | TC_GETLINE | TC_FUNCDECL | TC_BEGIN | TC_END)
     260#define TC_WORD    (TC_IN | TC_STATEMNT | TC_ELSE \
     261                   | TC_BUILTIN | TC_LENGTH | TC_GETLINE \
     262                   | TC_FUNCDECL | TC_BEGIN | TC_END)
    203263
    204264/* discard newlines after these */
     
    295355#define NTCC    '\377'
    296356
    297 #define OC_B  OC_BUILTIN
    298 
    299357static const char tokenlist[] ALIGN1 =
    300     "\1("         NTC
    301     "\1)"         NTC
    302     "\1/"         NTC                                   /* REGEXP */
    303     "\2>>"        "\1>"         "\1|"       NTC         /* OUTRDR */
    304     "\2++"        "\2--"        NTC                     /* UOPPOST */
    305     "\2++"        "\2--"        "\1$"       NTC         /* UOPPRE1 */
    306     "\2=="        "\1="         "\2+="      "\2-="      /* BINOPX */
     358    "\1("         NTC                                   /* TC_SEQSTART */
     359    "\1)"         NTC                                   /* TC_SEQTERM */
     360    "\1/"         NTC                                   /* TC_REGEXP */
     361    "\2>>"        "\1>"         "\1|"       NTC         /* TC_OUTRDR */
     362    "\2++"        "\2--"        NTC                     /* TC_UOPPOST */
     363    "\2++"        "\2--"        "\1$"       NTC         /* TC_UOPPRE1 */
     364    "\2=="        "\1="         "\2+="      "\2-="      /* TC_BINOPX */
    307365    "\2*="        "\2/="        "\2%="      "\2^="
    308366    "\1+"         "\1-"         "\3**="     "\2**"
     
    311369    "\1<"         "\2!~"        "\1~"       "\2&&"
    312370    "\2||"        "\1?"         "\1:"       NTC
    313     "\2in"        NTC
    314     "\1,"         NTC
    315     "\1|"         NTC
    316     "\1+"         "\1-"         "\1!"       NTC         /* UOPPRE2 */
    317     "\1]"         NTC
    318     "\1{"         NTC
    319     "\1}"         NTC
    320     "\1;"         NTC
    321     "\1\n"        NTC
    322     "\2if"        "\2do"        "\3for"     "\5break"   /* STATX */
     371    "\2in"        NTC                                   /* TC_IN */
     372    "\1,"         NTC                                   /* TC_COMMA */
     373    "\1|"         NTC                                   /* TC_PIPE */
     374    "\1+"         "\1-"         "\1!"       NTC         /* TC_UOPPRE2 */
     375    "\1]"         NTC                                   /* TC_ARRTERM */
     376    "\1{"         NTC                                   /* TC_GRPSTART */
     377    "\1}"         NTC                                   /* TC_GRPTERM */
     378    "\1;"         NTC                                   /* TC_SEMICOL */
     379    "\1\n"        NTC                                   /* TC_NEWLINE */
     380    "\2if"        "\2do"        "\3for"     "\5break"   /* TC_STATX */
    323381    "\10continue" "\6delete"    "\5print"
    324382    "\6printf"    "\4next"      "\10nextfile"
    325383    "\6return"    "\4exit"      NTC
    326     "\5while"     NTC
    327     "\4else"      NTC
    328 
    329     "\3and"       "\5compl"     "\6lshift"  "\2or"
     384    "\5while"     NTC                                   /* TC_WHILE */
     385    "\4else"      NTC                                   /* TC_ELSE */
     386    "\3and"       "\5compl"     "\6lshift"  "\2or"      /* TC_BUILTIN */
    330387    "\6rshift"    "\3xor"
    331     "\5close"     "\6system"    "\6fflush"  "\5atan2"   /* BUILTIN */
     388    "\5close"     "\6system"    "\6fflush"  "\5atan2"
    332389    "\3cos"       "\3exp"       "\3int"     "\3log"
    333390    "\4rand"      "\3sin"       "\4sqrt"    "\5srand"
    334     "\6gensub"    "\4gsub"      "\5index"   "\6length"
     391    "\6gensub"    "\4gsub"      "\5index"   /* "\6length" was here */
    335392    "\5match"     "\5split"     "\7sprintf" "\3sub"
    336393    "\6substr"    "\7systime"   "\10strftime" "\6mktime"
    337394    "\7tolower"   "\7toupper"   NTC
    338     "\7getline"   NTC
    339     "\4func"      "\10function" NTC
    340     "\5BEGIN"     NTC
    341     "\3END"
     395    "\6length"    NTC                                   /* TC_LENGTH */
     396    "\7getline"   NTC                                   /* TC_GETLINE */
     397    "\4func"      "\10function" NTC                     /* TC_FUNCDECL */
     398    "\5BEGIN"     NTC                                   /* TC_BEGIN */
     399    "\3END"                                             /* TC_END */
    342400    /* compiler adds trailing "\0" */
    343401    ;
     402
     403#define OC_B  OC_BUILTIN
    344404
    345405static const uint32_t tokeninfo[] = {
     
    357417    OC_COMPARE|VV|P(39)|2,   OC_MATCH|Sx|P(45)|'!',   OC_MATCH|Sx|P(45)|'~',   OC_LAND|Vx|P(55),
    358418    OC_LOR|Vx|P(59),         OC_TERNARY|Vx|P(64)|'?', OC_COLON|xx|P(67)|':',
    359     OC_IN|SV|P(49), /* in */
     419    OC_IN|SV|P(49), /* TC_IN */
    360420    OC_COMMA|SS|P(80),
    361421    OC_PGETLINE|SV|P(37),
     
    372432    ST_WHILE,
    373433    0, /* else */
    374 
    375434    OC_B|B_an|P(0x83), OC_B|B_co|P(0x41), OC_B|B_ls|P(0x83), OC_B|B_or|P(0x83),
    376435    OC_B|B_rs|P(0x83), OC_B|B_xo|P(0x83),
     
    378437    OC_FBLTIN|Nx|F_co, OC_FBLTIN|Nx|F_ex, OC_FBLTIN|Nx|F_in, OC_FBLTIN|Nx|F_lg,
    379438    OC_FBLTIN|F_rn,    OC_FBLTIN|Nx|F_si, OC_FBLTIN|Nx|F_sq, OC_FBLTIN|Nx|F_sr,
    380     OC_B|B_ge|P(0xd6), OC_B|B_gs|P(0xb6), OC_B|B_ix|P(0x9b), OC_FBLTIN|Sx|F_le,
     439    OC_B|B_ge|P(0xd6), OC_B|B_gs|P(0xb6), OC_B|B_ix|P(0x9b), /* OC_FBLTIN|Sx|F_le, was here */
    381440    OC_B|B_ma|P(0x89), OC_B|B_sp|P(0x8b), OC_SPRINTF,        OC_B|B_su|P(0xb6),
    382441    OC_B|B_ss|P(0x8f), OC_FBLTIN|F_ti,    OC_B|B_ti|P(0x0b), OC_B|B_mt|P(0x0b),
    383442    OC_B|B_lo|P(0x49), OC_B|B_up|P(0x49),
     443    OC_FBLTIN|Sx|F_le, /* TC_LENGTH */
    384444    OC_GETLINE|SV|P(0),
    385445    0,                 0,
    386446    0,
    387     0 /* END */
     447    0 /* TC_END */
    388448};
    389449
     
    10091069    if (t_rollback) {
    10101070        t_rollback = FALSE;
    1011 
    10121071    } else if (concat_inserted) {
    10131072        concat_inserted = FALSE;
    10141073        t_tclass = save_tclass;
    10151074        t_info = save_info;
    1016 
    10171075    } else {
    10181076        p = g_pos;
     
    10301088            tc = TC_EOF;
    10311089            debug_printf_parse("%s: token found: TC_EOF\n", __func__);
    1032 
    10331090        } else if (*p == '\"') {
    10341091            /* it's a string */
     
    10461103            tc = TC_STRING;
    10471104            debug_printf_parse("%s: token found:'%s' TC_STRING\n", __func__, t_string);
    1048 
    10491105        } else if ((expected & TC_REGEXP) && *p == '/') {
    10501106            /* it's regexp */
     
    10791135            tc = TC_NUMBER;
    10801136            debug_printf_parse("%s: token found:%f TC_NUMBER\n", __func__, t_double);
    1081 
    10821137        } else {
    10831138            /* search for something known */
     
    11561211
    11571212    /* Are we ready for this? */
    1158     if (!(ltclass & expected))
     1213    if (!(ltclass & expected)) {
    11591214        syntax_error((ltclass & (TC_NEWLINE | TC_EOF)) ?
    11601215                EMSG_UNEXP_EOS : EMSG_UNEXP_TOKEN);
     1216    }
    11611217
    11621218    return ltclass;
     
    13251381                    cn->l.n = condition();
    13261382                    break;
     1383
     1384                case TC_LENGTH:
     1385                    debug_printf_parse("%s: TC_LENGTH\n", __func__);
     1386                    next_token(TC_SEQSTART | TC_OPTERM | TC_GRPTERM);
     1387                    rollback_token();
     1388                    if (t_tclass & TC_SEQSTART) {
     1389                        /* It was a "(" token. Handle just like TC_BUILTIN */
     1390                        cn->l.n = condition();
     1391                    }
     1392                    break;
    13271393                }
    13281394            }
     
    14891555            n = chain_node(OC_EXEC);
    14901556            n->a.n = break_ptr;
     1557            chain_expr(t_info);
    14911558            break;
    14921559
     
    14951562            n = chain_node(OC_EXEC);
    14961563            n->a.n = continue_ptr;
     1564            chain_expr(t_info);
    14971565            break;
    14981566
     
    15271595            seq = &beginseq;
    15281596            chain_group();
    1529 
    15301597        } else if (tclass & TC_END) {
    15311598            debug_printf_parse("%s: TC_END\n", __func__);
    15321599            seq = &endseq;
    15331600            chain_group();
    1534 
    15351601        } else if (tclass & TC_FUNCDECL) {
    15361602            debug_printf_parse("%s: TC_FUNCDECL\n", __func__);
     
    15501616            chain_group();
    15511617            clear_array(ahash);
    1552 
    15531618        } else if (tclass & TC_OPSEQ) {
    15541619            debug_printf_parse("%s: TC_OPSEQ\n", __func__);
     
    15651630            }
    15661631            cn->r.n = mainseq.last;
    1567 
    15681632        } else /* if (tclass & TC_GRPSTART) */ {
    15691633            debug_printf_parse("%s: TC_GRPSTART(?)\n", __func__);
     
    18261890
    18271891        mk_splitter(getvar_s(v), &fsplitter);
    1828 
    18291892    } else if (v == intvar[RS]) {
    18301893        mk_splitter(getvar_s(v), &rsplitter);
    1831 
    18321894    } else if (v == intvar[IGNORECASE]) {
    18331895        icase = istrue(v);
    1834 
    18351896    } else {                /* $n */
    18361897        n = getvar_i(intvar[NF]);
     
    20162077    const char *s = format;
    20172078
    2018     if (int_as_int && n == (int)n) {
    2019         r = snprintf(b, size, "%d", (int)n);
     2079    if (int_as_int && n == (long long)n) {
     2080        r = snprintf(b, size, "%lld", (long long)n);
    20202081    } else {
    20212082        do { c = *s; } while (c && *++s);
     
    26622723            const char *sv_progname;
    26632724
    2664             if (!op->r.f->body.first)
     2725            /* The body might be empty, still has to eval the args */
     2726            if (!op->r.n->info && !op->r.f->body.first)
    26652727                syntax_error(EMSG_UNDEF_FUNC);
    26662728
     
    27332795            switch (opn) {
    27342796            case F_in:
    2735                 R_d = (int)L_d;
     2797                R_d = (long long)L_d;
    27362798                break;
    27372799
     
    27842846
    27852847            case F_le:
    2786                 if (!op1)
     2848                debug_printf_eval("length: L.s:'%s'\n", L.s);
     2849                if (!op1) {
    27872850                    L.s = getvar_s(intvar[F0]);
     2851                    debug_printf_eval("length: L.s='%s'\n", L.s);
     2852                }
     2853                else if (L.v->type & VF_ARRAY) {
     2854                    R_d = L.v->x.array->nel;
     2855                    debug_printf_eval("length: array_len:%d\n", L.v->x.array->nel);
     2856                    break;
     2857                }
    27882858                R_d = strlen(L.s);
    27892859                break;
     
    29313001                if (R_d == 0)
    29323002                    syntax_error(EMSG_DIV_BY_ZERO);
    2933                 L_d -= (int)(L_d / R_d) * R_d;
     3003                L_d -= (long long)(L_d / R_d) * R_d;
    29343004                break;
    29353005            }
     
    30793149    llist_t *list_v = NULL;
    30803150    llist_t *list_f = NULL;
     3151#if ENABLE_FEATURE_AWK_GNU_EXTENSIONS
     3152    llist_t *list_e = NULL;
     3153#endif
    30813154    int i, j;
    30823155    var *v;
     
    31373210        }
    31383211    }
    3139     opt_complementary = "v::f::"; /* -v and -f can occur multiple times */
    3140     opt = getopt32(argv, "F:v:f:W:", &opt_F, &list_v, &list_f, NULL);
     3212    opt_complementary = OPTCOMPLSTR_AWK;
     3213    opt = getopt32(argv, OPTSTR_AWK, &opt_F, &list_v, &list_f, IF_FEATURE_AWK_GNU_EXTENSIONS(&list_e,) NULL);
    31413214    argv += optind;
    31423215    argc -= optind;
    3143     if (opt & 0x1) { /* -F */
     3216    if (opt & OPT_W)
     3217        bb_error_msg("warning: option -W is ignored");
     3218    if (opt & OPT_F) {
    31443219        unescape_string_in_place(opt_F);
    31453220        setvar_s(intvar[FS], opt_F);
    31463221    }
    3147     while (list_v) { /* -v */
     3222    while (list_v) {
    31483223        if (!is_assignment(llist_pop(&list_v)))
    31493224            bb_show_usage();
    31503225    }
    3151     if (list_f) { /* -f */
    3152         do {
    3153             char *s = NULL;
    3154             FILE *from_file;
    3155 
    3156             g_progname = llist_pop(&list_f);
    3157             from_file = xfopen_stdin(g_progname);
    3158             /* one byte is reserved for some trick in next_token */
    3159             for (i = j = 1; j > 0; i += j) {
    3160                 s = xrealloc(s, i + 4096);
    3161                 j = fread(s + i, 1, 4094, from_file);
    3162             }
    3163             s[i] = '\0';
    3164             fclose(from_file);
    3165             parse_program(s + 1);
    3166             free(s);
    3167         } while (list_f);
    3168         argc++;
    3169     } else { // no -f: take program from 1st parameter
    3170         if (!argc)
     3226    while (list_f) {
     3227        char *s = NULL;
     3228        FILE *from_file;
     3229
     3230        g_progname = llist_pop(&list_f);
     3231        from_file = xfopen_stdin(g_progname);
     3232        /* one byte is reserved for some trick in next_token */
     3233        for (i = j = 1; j > 0; i += j) {
     3234            s = xrealloc(s, i + 4096);
     3235            j = fread(s + i, 1, 4094, from_file);
     3236        }
     3237        s[i] = '\0';
     3238        fclose(from_file);
     3239        parse_program(s + 1);
     3240        free(s);
     3241    }
     3242    g_progname = "cmd. line";
     3243#if ENABLE_FEATURE_AWK_GNU_EXTENSIONS
     3244    while (list_e) {
     3245        parse_program(llist_pop(&list_e));
     3246    }
     3247#endif
     3248    if (!(opt & (OPT_f | OPT_e))) {
     3249        if (!*argv)
    31713250            bb_show_usage();
    3172         g_progname = "cmd. line";
    31733251        parse_program(*argv++);
    3174     }
    3175     if (opt & 0x8) // -W
    3176         bb_error_msg("warning: option -W is ignored");
     3252        argc--;
     3253    }
    31773254
    31783255    /* fill in ARGV array */
    3179     setvar_i(intvar[ARGC], argc);
     3256    setvar_i(intvar[ARGC], argc + 1);
    31803257    setari_u(intvar[ARGV], 0, "awk");
    31813258    i = 0;
  • branches/3.3/mindi-busybox/editors/cmp.c

    r3232 r3621  
    1010/* BB_AUDIT SUSv3 (virtually) compliant -- uses nicer GNU format for -l. */
    1111/* http://www.opengroup.org/onlinepubs/007904975/utilities/cmp.html */
     12
     13//config:config CMP
     14//config:   bool "cmp"
     15//config:   default y
     16//config:   help
     17//config:     cmp is used to compare two files and returns the result
     18//config:     to standard output.
     19
     20//kbuild:lib-$(CONFIG_CMP) += cmp.o
     21
     22//applet:IF_CMP(APPLET(cmp, BB_DIR_USR_BIN, BB_SUID_DROP))
    1223
    1324//usage:#define cmp_trivial_usage
  • branches/3.3/mindi-busybox/editors/diff.c

    r3232 r3621  
    7777 */
    7878
     79//config:config DIFF
     80//config:   bool "diff"
     81//config:   default y
     82//config:   help
     83//config:     diff compares two files or directories and outputs the
     84//config:     differences between them in a form that can be given to
     85//config:     the patch command.
     86//config:
     87//config:config FEATURE_DIFF_LONG_OPTIONS
     88//config:   bool "Enable long options"
     89//config:   default y
     90//config:   depends on DIFF && LONG_OPTS
     91//config:   help
     92//config:     Enable use of long options.
     93//config:
     94//config:config FEATURE_DIFF_DIR
     95//config:   bool "Enable directory support"
     96//config:   default y
     97//config:   depends on DIFF
     98//config:   help
     99//config:     This option enables support for directory and subdirectory
     100//config:     comparison.
     101
     102//kbuild:lib-$(CONFIG_DIFF) += diff.o
     103
     104//applet:IF_DIFF(APPLET(diff, BB_DIR_USR_BIN, BB_SUID_DROP))
     105
    79106//usage:#define diff_trivial_usage
    80107//usage:       "[-abBdiNqrTstw] [-L LABEL] [-S FILE] [-U LINES] FILE1 FILE2"
     
    99126
    100127#include "libbb.h"
     128#include "common_bufsiz.h"
    101129
    102130#if 0
     
    337365
    338366struct line {
    339     /* 'serial' is not used in the begining, so we reuse it
     367    /* 'serial' is not used in the beginning, so we reuse it
    340368     * to store line offsets, thus reducing memory pressure
    341369     */
     
    407435            int c = fgetc(ft->ft_fp);
    408436            if (c == EOF) {
    409                 printf("\n\\ No newline at end of file\n");
     437                puts("\n\\ No newline at end of file");
    410438                return;
    411439            }
     
    632660
    633661                for (j = 0; j < 2; j++)
    634                     for (k = v[j].a; k < v[j].b; k++)
    635                         nonempty |= (ix[j][k+1] - ix[j][k] != 1);
     662                    for (k = v[j].a; k <= v[j].b; k++)
     663                        nonempty |= (ix[j][k] - ix[j][k - 1] != 1);
    636664
    637665                vec = xrealloc_vector(vec, 6, ++idx);
     
    666694                printf(",%d", (a < b) ? b - a + 1 : 0);
    667695            }
    668             printf(" @@\n");
     696            puts(" @@");
    669697            /*
    670698             * Output changes in "unified" diff format--the old and new lines
     
    714742            if (bb_copyfd_eof(fd, fd_tmp) < 0)
    715743                xfunc_die();
    716             if (fd) /* Prevents closing of stdin */
     744            if (fd != STDIN_FILENO)
    717745                close(fd);
    718746            fd = fd_tmp;
     747            xlseek(fd, 0, SEEK_SET);
    719748        }
    720749        fp[i] = fdopen(fd, "r");
    721750    }
    722751
     752    setup_common_bufsiz();
    723753    while (1) {
    724754        const size_t sz = COMMON_BUFSIZE / 2;
  • branches/3.3/mindi-busybox/editors/ed.c

    r3232 r3621  
    88 */
    99
     10//config:config ED
     11//config:   bool "ed"
     12//config:   default y
     13//config:   help
     14//config:     The original 1970's Unix text editor, from the days of teletypes.
     15//config:     Small, simple, evil. Part of SUSv3. If you're not already using
     16//config:     this, you don't need it.
     17
     18//kbuild:lib-$(CONFIG_ED) += ed.o
     19
     20//applet:IF_ED(APPLET(ed, BB_DIR_BIN, BB_SUID_DROP))
     21
    1022//usage:#define ed_trivial_usage ""
    1123//usage:#define ed_full_usage ""
    1224
    1325#include "libbb.h"
     26#include "common_bufsiz.h"
    1427
    1528typedef struct LINE {
     
    2437
    2538enum {
    26     USERSIZE = sizeof(searchString) > 1024 ? 1024
    27              : sizeof(searchString) - 1, /* max line length typed in by user */
     39    USERSIZE = COMMON_BUFSIZE > 1024 ? 1024
     40             : COMMON_BUFSIZE - 1, /* max line length typed in by user */
    2841    INITBUF_SIZE = 1024, /* initial buffer size */
    2942};
     
    5568#define marks              (G.marks             )
    5669#define INIT_G() do { \
     70    setup_common_bufsiz(); \
    5771    SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
    5872} while (0)
     
    195209                    printf("\"%s\"\n", fileName);
    196210                else
    197                     printf("No file name\n");
     211                    puts("No file name");
    198212                break;
    199213            }
     
    721735        bufUsed += cc;
    722736        bufPtr = bufBase;
    723 
    724737    } while (cc > 0);
    725738
  • branches/3.3/mindi-busybox/editors/patch.c

    r3232 r3621  
    346346// state 2: In hunk: counting initial context lines
    347347// state 3: In hunk: getting body
     348// Like GNU patch, we don't require a --- line before the +++, and
     349// also allow the --- after the +++ line.
    348350
    349351int patch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
     
    371373        }
    372374    }
    373     if (argv[0]) {
    374         oldname = xstrdup(argv[0]);
    375         newname = xstrdup(argv[0]);
    376     }
    377375
    378376    // Loop through the lines in the patch
     
    413411
    414412        // Open a new file?
    415         if (!strncmp("--- ", patchline, 4) || !strncmp("+++ ", patchline, 4)) {
     413        if (is_prefixed_with(patchline, "--- ") || is_prefixed_with(patchline, "+++ ")) {
    416414            char *s, **name = reverse ? &newname : &oldname;
    417415            int i;
     
    445443        // Start a new hunk?  Usually @@ -oldline,oldlen +newline,newlen @@
    446444        // but a missing ,value means the value is 1.
    447         } else if (state == 1 && !strncmp("@@ -", patchline, 4)) {
     445        } else if (state == 1 && is_prefixed_with(patchline, "@@ -")) {
    448446            int i;
    449447            char *s = patchline+4;
     
    463461            state = 2;
    464462
     463            // If the --- line is missing or malformed, either oldname
     464            // or (for -R) newname could be NULL -- but not both.  Like
     465            // GNU patch, proceed based on the +++ line, and avoid SEGVs.
     466            if (!oldname)
     467                oldname = xstrdup("MISSING_FILENAME");
     468            if (!newname)
     469                newname = xstrdup("MISSING_FILENAME");
     470
    465471            // If this is the first hunk, open the file.
    466472            if (TT.filein == -1) {
     
    477483                if (!strcmp(name, "/dev/null") || !(reverse ? oldsum : newsum)) {
    478484                    name = reverse ? newname : oldname;
    479                     empty++;
     485                    empty = 1;
    480486                }
    481487
    482                 // handle -p path truncation.
     488                // Handle -p path truncation.
    483489                for (i = 0, s = name; *s;) {
    484490                    if ((option_mask32 & FLAG_PATHLEN) && TT.prefix == i)
     
    491497                    name = s;
    492498                }
     499                // If "patch FILE_TO_PATCH", completely ignore name from patch
     500                if (argv[0])
     501                    name = argv[0];
    493502
    494503                if (empty) {
  • branches/3.3/mindi-busybox/editors/patch_bbox.c

    r2725 r3621  
    189189            unsigned dst_last_line = 1;
    190190
    191             if ((sscanf(patch_line, "@@ -%d,%d +%d,%d", &src_beg_line, &src_last_line, &dst_beg_line, &dst_last_line) < 3)
    192              && (sscanf(patch_line, "@@ -%d +%d,%d", &src_beg_line, &dst_beg_line, &dst_last_line) < 2)
     191            if ((sscanf(patch_line, "@@ -%u,%u +%u,%u", &src_beg_line, &src_last_line, &dst_beg_line, &dst_last_line) < 3)
     192             && (sscanf(patch_line, "@@ -%u +%u,%u", &src_beg_line, &dst_beg_line, &dst_last_line) < 2)
    193193            ) {
    194194                /* No more hunks for this file */
  • branches/3.3/mindi-busybox/editors/sed.c

    r3232 r3621  
    2323 * resulting sed_cmd_t structures are appended to a linked list
    2424 * (G.sed_cmd_head/G.sed_cmd_tail).
    25  *
    26  * add_input_file() adds a FILE* to the list of input files.  We need to
    27  * know all input sources ahead of time to find the last line for the $ match.
    2825 *
    2926 * process_files() does actual sedding, reading data lines from each input FILE*
     
    5754 * http://www.opengroup.org/onlinepubs/007904975/utilities/sed.html
    5855 * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html
     56 * http://sed.sourceforge.net/sedfaq3.html
    5957 */
    6058
     59//config:config SED
     60//config:   bool "sed"
     61//config:   default y
     62//config:   help
     63//config:     sed is used to perform text transformations on a file
     64//config:     or input from a pipeline.
     65
     66//kbuild:lib-$(CONFIG_SED) += sed.o
     67
     68//applet:IF_SED(APPLET(sed, BB_DIR_BIN, BB_SUID_DROP))
     69
    6170//usage:#define sed_trivial_usage
    62 //usage:       "[-inr] [-f FILE]... [-e CMD]... [FILE]...\n"
    63 //usage:       "or: sed [-inr] CMD [FILE]..."
     71//usage:       "[-inrE] [-f FILE]... [-e CMD]... [FILE]...\n"
     72//usage:       "or: sed [-inrE] CMD [FILE]..."
    6473//usage:#define sed_full_usage "\n\n"
    6574//usage:       "    -e CMD  Add CMD to sed commands to be executed"
     
    6877//usage:     "\n        Optionally back files up, appending SFX"
    6978//usage:     "\n    -n  Suppress automatic printing of pattern space"
    70 //usage:     "\n    -r  Use extended regex syntax"
     79//usage:     "\n    -r,-E   Use extended regex syntax"
    7180//usage:     "\n"
    7281//usage:     "\nIf no -e or -f, the first non-option argument is the sed command string."
     
    7887
    7988#include "libbb.h"
     89#include "common_bufsiz.h"
    8090#include "xregex.h"
    8191
     
    102112    int beg_line;           /* 'sed 1p'   0 == apply commands to all lines */
    103113    int beg_line_orig;      /* copy of the above, needed for -i */
    104     int end_line;           /* 'sed 1,3p' 0 == one line only. -1 = last line ($) */
    105 
    106     FILE *sw_file;          /* File (sw) command writes to, -1 for none. */
     114    int end_line;           /* 'sed 1,3p' 0 == one line only. -1 = last line ($). -2-N = +N */
     115    int end_line_orig;
     116
     117    FILE *sw_file;          /* File (sw) command writes to, NULL for none. */
    107118    char *string;           /* Data string for (saicytb) commands. */
    108119
     
    125136    /* options */
    126137    int be_quiet, regex_type;
     138
    127139    FILE *nonstdout;
    128140    char *outname, *hold_space;
    129 
    130     /* List of input files */
    131     int input_file_count, current_input_file;
    132     FILE **input_file_list;
     141    smallint exitcode;
     142
     143    /* list of input files */
     144    int current_input_file, last_input_file;
     145    char **input_file_list;
     146    FILE *current_fp;
    133147
    134148    regmatch_t regmatch[10];
     
    138152    sed_cmd_t *sed_cmd_head, **sed_cmd_tail;
    139153
    140     /* Linked list of append lines */
     154    /* linked list of append lines */
    141155    llist_t *append_head;
    142156
     
    149163    } pipeline;
    150164} FIX_ALIASING;
    151 #define G (*(struct globals*)&bb_common_bufsiz1)
    152 struct BUG_G_too_big {
    153     char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
    154 };
     165#define G (*(struct globals*)bb_common_bufsiz1)
    155166#define INIT_G() do { \
     167    setup_common_bufsiz(); \
     168    BUILD_BUG_ON(sizeof(G) > COMMON_BUFSIZE); \
    156169    G.sed_cmd_tail = &G.sed_cmd_head; \
    157170} while (0)
     
    169182
    170183        if (sed_cmd->sw_file)
    171             xprint_and_close_file(sed_cmd->sw_file);
     184            fclose(sed_cmd->sw_file);
    172185
    173186        if (sed_cmd->beg_match) {
     
    190203    free(G.hold_space);
    191204
    192     while (G.current_input_file < G.input_file_count)
    193         fclose(G.input_file_list[G.current_input_file++]);
     205    if (G.current_fp)
     206        fclose(G.current_fp);
    194207}
    195208#else
     
    206219/* strcpy, replacing "\from" with 'to'. If to is NUL, replacing "\any" with 'any' */
    207220
    208 static void parse_escapes(char *dest, const char *string, int len, char from, char to)
    209 {
     221static unsigned parse_escapes(char *dest, const char *string, int len, char from, char to)
     222{
     223    char *d = dest;
    210224    int i = 0;
     225
     226    if (len == -1)
     227        len = strlen(string);
    211228
    212229    while (i < len) {
    213230        if (string[i] == '\\') {
    214231            if (!to || string[i+1] == from) {
    215                 *dest++ = to ? to : string[i+1];
     232                if ((*d = to ? to : string[i+1]) == '\0')
     233                    return d - dest;
    216234                i += 2;
     235                d++;
    217236                continue;
    218237            }
    219             *dest++ = string[i++];
    220         }
    221         /* TODO: is it safe wrt a string with trailing '\\' ? */
    222         *dest++ = string[i++];
    223     }
    224     *dest = '\0';
     238            i++; /* skip backslash in string[] */
     239            *d++ = '\\';
     240            /* fall through: copy next char verbatim */
     241        }
     242        if ((*d = string[i++]) == '\0')
     243            return d - dest;
     244        d++;
     245    }
     246    *d = '\0';
     247    return d - dest;
    225248}
    226249
     
    233256    /* GNU sed also recognizes \t and \r */
    234257    for (s = "\nn\tt\rr"; *s; s += 2) {
    235         parse_escapes(dest, string, len, s[1], s[0]);
     258        len = parse_escapes(dest, string, len, s[1], s[0]);
    236259        string = dest;
    237         len = strlen(dest);
    238260    }
    239261    return dest;
     
    331353        temp = copy_parsing_escapes(pos, next);
    332354        *regex = xzalloc(sizeof(regex_t));
    333         xregcomp(*regex, temp, G.regex_type|REG_NEWLINE);
     355        xregcomp(*regex, temp, G.regex_type);
    334356        free(temp);
    335357        /* Move position to next character after last delimiter */
     
    371393    /*
    372394     * A substitution command should look something like this:
    373      *    s/match/replace/ #gIpw
     395     *    s/match/replace/ #giIpw
    374396     *    ||     |        |||
    375397     *    mandatory       optional
     
    385407
    386408    sed_cmd->which_match = 1;
     409    dbg("s flags:'%s'", substr + idx + 1);
    387410    while (substr[++idx]) {
     411        dbg("s flag:'%c'", substr[idx]);
    388412        /* Parse match number */
    389413        if (isdigit(substr[idx])) {
     
    393417/* FIXME: error check? */
    394418                sed_cmd->which_match = (unsigned)strtol(substr+idx, (char**) &pos, 10);
    395                 idx = pos - substr;
     419                idx = pos - substr - 1;
    396420            }
    397421            continue;
     
    414438        case 'w':
    415439        {
    416             char *temp;
    417             idx += parse_file_cmd(/*sed_cmd,*/ substr+idx, &temp);
     440            char *fname;
     441            idx += parse_file_cmd(/*sed_cmd,*/ substr+idx+1, &fname);
     442            sed_cmd->sw_file = xfopen_for_write(fname);
     443            sed_cmd->sw_last_char = '\n';
     444            free(fname);
    418445            break;
    419446        }
    420447        /* Ignore case (gnu exension) */
     448        case 'i':
    421449        case 'I':
    422450            cflags |= REG_ICASE;
     
    432460            goto out;
    433461        default:
     462            dbg("s bad flags:'%s'", substr + idx);
    434463            bb_error_msg_and_die("bad option in substitution expression");
    435464        }
     
    454483static const char *parse_cmd_args(sed_cmd_t *sed_cmd, const char *cmdstr)
    455484{
    456     static const char cmd_letters[] = "saicrw:btTydDgGhHlnNpPqx={}";
     485    static const char cmd_letters[] ALIGN1 = "saicrw:btTydDgGhHlnNpPqx={}";
    457486    enum {
    458487        IDX_s = 0,
     
    485514        IDX_nul
    486515    };
    487     struct chk { char chk[sizeof(cmd_letters)-1 == IDX_nul ? 1 : -1]; };
    488 
    489     unsigned idx = strchrnul(cmd_letters, sed_cmd->cmd) - cmd_letters;
     516    unsigned idx;
     517
     518    BUILD_BUG_ON(sizeof(cmd_letters)-1 != IDX_nul);
     519
     520    idx = strchrnul(cmd_letters, sed_cmd->cmd) - cmd_letters;
    490521
    491522    /* handle (s)ubstitution command */
     
    495526    /* handle edit cmds: (a)ppend, (i)nsert, and (c)hange */
    496527    else if (idx <= IDX_c) { /* a,i,c */
     528        unsigned len;
     529
    497530        if (idx < IDX_c) { /* a,i */
    498531            if (sed_cmd->end_line || sed_cmd->end_match)
     
    508541            cmdstr++;
    509542        }
    510         sed_cmd->string = xstrdup(cmdstr);
     543        len = strlen(cmdstr);
     544        sed_cmd->string = copy_parsing_escapes(cmdstr, len);
     545        cmdstr += len;
    511546        /* "\anychar" -> "anychar" */
    512         parse_escapes(sed_cmd->string, sed_cmd->string, strlen(cmdstr), '\0', '\0');
    513         cmdstr += strlen(cmdstr);
     547        parse_escapes(sed_cmd->string, sed_cmd->string, -1, '\0', '\0');
    514548    }
    515549    /* handle file cmds: (r)ead */
     
    543577        cmdstr += parse_regex_delim(cmdstr, &match, &replace)+1;
    544578        /* \n already parsed, but \delimiter needs unescaping. */
    545         parse_escapes(match, match, strlen(match), i, i);
    546         parse_escapes(replace, replace, strlen(replace), i, i);
     579        parse_escapes(match,   match,   -1, i, i);
     580        parse_escapes(replace, replace, -1, i, i);
    547581
    548582        sed_cmd->string = xzalloc((strlen(match) + 1) * 2);
     
    626660
    627661            cmdstr++;
    628             idx = get_address(cmdstr, &sed_cmd->end_line, &sed_cmd->end_match);
    629             if (!idx)
     662            if (*cmdstr == '+' && isdigit(cmdstr[1])) {
     663                /* http://sed.sourceforge.net/sedfaq3.html#s3.3
     664                 * Under GNU sed 3.02+, ssed, and sed15+, <address2>
     665                 * may also be a notation of the form +num,
     666                 * indicating the next num lines after <address1> is
     667                 * matched.
     668                 * GNU sed 4.2.1 accepts even "+" (meaning "+0").
     669                 * We don't (we check for isdigit, see above), think
     670                 * about the "+-3" case.
     671                 */
     672                char *end;
     673                /* code is smaller compared to using &cmdstr here: */
     674                idx = strtol(cmdstr+1, &end, 10);
     675                sed_cmd->end_line = -2 - idx;
     676                cmdstr = end;
     677            } else {
     678                idx = get_address(cmdstr, &sed_cmd->end_line, &sed_cmd->end_match);
     679                cmdstr += idx;
     680                idx--; /* if 0, trigger error check below */
     681            }
     682            if (idx < 0)
    630683                bb_error_msg_and_die("no address after comma");
    631             cmdstr += idx;
     684            sed_cmd->end_line_orig = sed_cmd->end_line;
    632685        }
    633686
     
    649702        sed_cmd->cmd = *cmdstr++;
    650703        cmdstr = parse_cmd_args(sed_cmd, cmdstr);
     704
     705        /* cmdstr now points past args.
     706         * GNU sed requires a separator, if there are more commands,
     707         * else it complains "char N: extra characters after command".
     708         * Example: "sed 'p;d'". We also allow "sed 'pd'".
     709         */
    651710
    652711        /* Add the command to the command array */
     
    843902static void append(char *s)
    844903{
    845     llist_add_to_end(&G.append_head, xstrdup(s));
    846 }
    847 
    848 static void flush_append(void)
    849 {
    850     char *data;
    851 
    852     /* Output appended lines. */
    853     while ((data = (char *)llist_pop(&G.append_head))) {
    854         fprintf(G.nonstdout, "%s\n", data);
    855         free(data);
    856     }
    857 }
    858 
    859 static void add_input_file(FILE *file)
    860 {
    861     G.input_file_list = xrealloc_vector(G.input_file_list, 2, G.input_file_count);
    862     G.input_file_list[G.input_file_count++] = file;
    863 }
    864 
    865 /* Get next line of input from G.input_file_list, flushing append buffer and
    866  * noting if we ran out of files without a newline on the last line we read.
     904    llist_add_to_end(&G.append_head, s);
     905}
     906
     907/* Output line of text. */
     908/* Note:
     909 * The tricks with NO_EOL_CHAR and last_puts_char are there to emulate gnu sed.
     910 * Without them, we had this:
     911 * echo -n thingy >z1
     912 * echo -n again >z2
     913 * >znull
     914 * sed "s/i/z/" z1 z2 znull | hexdump -vC
     915 * output:
     916 * gnu sed 4.1.5:
     917 * 00000000  74 68 7a 6e 67 79 0a 61  67 61 7a 6e              |thzngy.agazn|
     918 * bbox:
     919 * 00000000  74 68 7a 6e 67 79 61 67  61 7a 6e                 |thzngyagazn|
    867920 */
    868921enum {
     
    870923    LAST_IS_NUL = 2,
    871924};
    872 static char *get_next_line(char *gets_char)
     925static void puts_maybe_newline(char *s, FILE *file, char *last_puts_char, char last_gets_char)
     926{
     927    char lpc = *last_puts_char;
     928
     929    /* Need to insert a '\n' between two files because first file's
     930     * last line wasn't terminated? */
     931    if (lpc != '\n' && lpc != '\0') {
     932        fputc('\n', file);
     933        lpc = '\n';
     934    }
     935    fputs(s, file);
     936
     937    /* 'x' - just something which is not '\n', '\0' or NO_EOL_CHAR */
     938    if (s[0])
     939        lpc = 'x';
     940
     941    /* had trailing '\0' and it was last char of file? */
     942    if (last_gets_char == LAST_IS_NUL) {
     943        fputc('\0', file);
     944        lpc = 'x'; /* */
     945    } else
     946    /* had trailing '\n' or '\0'? */
     947    if (last_gets_char != NO_EOL_CHAR) {
     948        fputc(last_gets_char, file);
     949        lpc = last_gets_char;
     950    }
     951
     952    if (ferror(file)) {
     953        xfunc_error_retval = 4;  /* It's what gnu sed exits with... */
     954        bb_error_msg_and_die(bb_msg_write_error);
     955    }
     956    *last_puts_char = lpc;
     957}
     958
     959static void flush_append(char *last_puts_char)
     960{
     961    char *data;
     962
     963    /* Output appended lines. */
     964    while ((data = (char *)llist_pop(&G.append_head)) != NULL) {
     965        /* Append command does not respect "nonterminated-ness"
     966         * of last line. Try this:
     967         * $ echo -n "woot" | sed -e '/woot/a woo' -
     968         * woot
     969         * woo
     970         * (both lines are terminated with \n)
     971         * Therefore we do not propagate "last_gets_char" here,
     972         * pass '\n' instead:
     973         */
     974        puts_maybe_newline(data, G.nonstdout, last_puts_char, '\n');
     975        free(data);
     976    }
     977}
     978
     979/* Get next line of input from G.input_file_list, flushing append buffer and
     980 * noting if we ran out of files without a newline on the last line we read.
     981 */
     982static char *get_next_line(char *gets_char, char *last_puts_char)
    873983{
    874984    char *temp = NULL;
     
    876986    char gc;
    877987
    878     flush_append();
     988    flush_append(last_puts_char);
    879989
    880990    /* will be returned if last line in the file
    881991     * doesn't end with either '\n' or '\0' */
    882992    gc = NO_EOL_CHAR;
    883     while (G.current_input_file < G.input_file_count) {
    884         FILE *fp = G.input_file_list[G.current_input_file];
     993    for (; G.current_input_file <= G.last_input_file; G.current_input_file++) {
     994        FILE *fp = G.current_fp;
     995        if (!fp) {
     996            const char *path = G.input_file_list[G.current_input_file];
     997            fp = stdin;
     998            if (path != bb_msg_standard_input) {
     999                fp = fopen_or_warn(path, "r");
     1000                if (!fp) {
     1001                    G.exitcode = EXIT_FAILURE;
     1002                    continue;
     1003                }
     1004            }
     1005            G.current_fp = fp;
     1006        }
    8851007        /* Read line up to a newline or NUL byte, inclusive,
    8861008         * return malloc'ed char[]. length of the chunk read
     
    9131035        }
    9141036        /* Close this file and advance to next one */
    915         fclose(fp);
    916         G.current_input_file++;
     1037        fclose_if_not_stdin(fp);
     1038        G.current_fp = NULL;
    9171039    }
    9181040    *gets_char = gc;
    9191041    return temp;
    920 }
    921 
    922 /* Output line of text. */
    923 /* Note:
    924  * The tricks with NO_EOL_CHAR and last_puts_char are there to emulate gnu sed.
    925  * Without them, we had this:
    926  * echo -n thingy >z1
    927  * echo -n again >z2
    928  * >znull
    929  * sed "s/i/z/" z1 z2 znull | hexdump -vC
    930  * output:
    931  * gnu sed 4.1.5:
    932  * 00000000  74 68 7a 6e 67 79 0a 61  67 61 7a 6e              |thzngy.agazn|
    933  * bbox:
    934  * 00000000  74 68 7a 6e 67 79 61 67  61 7a 6e                 |thzngyagazn|
    935  */
    936 static void puts_maybe_newline(char *s, FILE *file, char *last_puts_char, char last_gets_char)
    937 {
    938     char lpc = *last_puts_char;
    939 
    940     /* Need to insert a '\n' between two files because first file's
    941      * last line wasn't terminated? */
    942     if (lpc != '\n' && lpc != '\0') {
    943         fputc('\n', file);
    944         lpc = '\n';
    945     }
    946     fputs(s, file);
    947 
    948     /* 'x' - just something which is not '\n', '\0' or NO_EOL_CHAR */
    949     if (s[0])
    950         lpc = 'x';
    951 
    952     /* had trailing '\0' and it was last char of file? */
    953     if (last_gets_char == LAST_IS_NUL) {
    954         fputc('\0', file);
    955         lpc = 'x'; /* */
    956     } else
    957     /* had trailing '\n' or '\0'? */
    958     if (last_gets_char != NO_EOL_CHAR) {
    959         fputc(last_gets_char, file);
    960         lpc = last_gets_char;
    961     }
    962 
    963     if (ferror(file)) {
    964         xfunc_error_retval = 4;  /* It's what gnu sed exits with... */
    965         bb_error_msg_and_die(bb_msg_write_error);
    966     }
    967     *last_puts_char = lpc;
    9681042}
    9691043
     
    9901064
    9911065    /* Prime the pump */
    992     next_line = get_next_line(&next_gets_char);
     1066    next_line = get_next_line(&next_gets_char, &last_puts_char);
    9931067
    9941068    /* Go through every line in each file */
     
    10041078    /* Read one line in advance so we can act on the last line,
    10051079     * the '$' address */
    1006     next_line = get_next_line(&next_gets_char);
     1080    next_line = get_next_line(&next_gets_char, &last_puts_char);
    10071081    linenum++;
    10081082
     
    10631137
    10641138        if (matched) {
     1139            if (sed_cmd->end_line <= -2) {
     1140                /* address2 is +N, i.e. N lines from beg_line */
     1141                sed_cmd->end_line = linenum + (-sed_cmd->end_line - 2);
     1142            }
    10651143            /* once matched, "n,xxx" range is dead, disabling it */
    10661144            if (sed_cmd->beg_line > 0) {
    10671145                sed_cmd->beg_line = -2;
    10681146            }
     1147            dbg("end1:%d", sed_cmd->end_line ? sed_cmd->end_line == -1
     1148                        ? !next_line : (sed_cmd->end_line <= linenum)
     1149                    : !sed_cmd->end_match);
     1150            dbg("end2:%d", sed_cmd->end_match && old_matched
     1151                    && !regexec(sed_cmd->end_match,pattern_space, 0, NULL, 0));
    10691152            sed_cmd->in_match = !(
    10701153                /* has the ending line come, or is this a single address command? */
     
    11761259        /* Append line to linked list to be printed later */
    11771260        case 'a':
    1178             append(sed_cmd->string);
     1261            append(xstrdup(sed_cmd->string));
    11791262            break;
    11801263
     
    11981281            if (rfile) {
    11991282                char *line;
    1200 
    12011283                while ((line = xmalloc_fgetline(rfile))
    12021284                        != NULL)
    12031285                    append(line);
    1204                 xprint_and_close_file(rfile);
     1286                fclose(rfile);
    12051287            }
    12061288
     
    12231305                pattern_space = next_line;
    12241306                last_gets_char = next_gets_char;
    1225                 next_line = get_next_line(&next_gets_char);
     1307                next_line = get_next_line(&next_gets_char, &last_puts_char);
    12261308                substituted = 0;
    12271309                linenum++;
     
    12591341            strcpy(pattern_space + len+1, next_line);
    12601342            last_gets_char = next_gets_char;
    1261             next_line = get_next_line(&next_gets_char);
     1343            next_line = get_next_line(&next_gets_char, &last_puts_char);
    12621344            linenum++;
    12631345            break;
     
    13631445    /* Delete and such jump here. */
    13641446 discard_line:
    1365     flush_append();
     1447    flush_append(&last_puts_char /*,last_gets_char*/);
    13661448    free(pattern_space);
    13671449
     
    13721454 * newlines.  This counts as multiple command lines.
    13731455 * However, newline can be escaped: 's/e/z\<newline>z/'
    1374  * We check for this.
     1456 * add_cmd() handles this.
    13751457 */
    13761458
     
    13821464    do {
    13831465        eol = strchr(cmdstr, '\n');
    1384  next:
    1385         if (eol) {
    1386             /* Count preceding slashes */
    1387             int slashes = 0;
    1388             char *sl = eol;
    1389 
    1390             while (sl != cmdstr && *--sl == '\\')
    1391                 slashes++;
    1392             /* Odd number of preceding slashes - newline is escaped */
    1393             if (slashes & 1) {
    1394                 overlapping_strcpy(eol - 1, eol);
    1395                 eol = strchr(eol, '\n');
    1396                 goto next;
    1397             }
     1466        if (eol)
    13981467            *eol = '\0';
    1399         }
    14001468        add_cmd(cmdstr);
    14011469        cmdstr = eol + 1;
     
    14221490#endif
    14231491
    1424     int status = EXIT_SUCCESS;
    1425 
    14261492    INIT_G();
    14271493
     
    14441510
    14451511    /* -i must be first, to match OPT_in_place definition */
    1446     opt = getopt32(argv, "i::rne:f:", &opt_i, &opt_e, &opt_f,
     1512    /* -E is a synonym of -r:
     1513     * GNU sed 4.2.1 mentions it in neither --help
     1514     * nor manpage, but does recognize it.
     1515     */
     1516    opt = getopt32(argv, "i::rEne:f:", &opt_i, &opt_e, &opt_f,
    14471517                &G.be_quiet); /* counter for -n */
    14481518    //argc -= optind;
     
    14511521        atexit(cleanup_outname);
    14521522    }
    1453     if (opt & 0x2) G.regex_type |= REG_EXTENDED; // -r
    1454     //if (opt & 0x4) G.be_quiet++; // -n
     1523    if (opt & (2|4))
     1524        G.regex_type |= REG_EXTENDED; // -r or -E
     1525    //if (opt & 8)
     1526    //  G.be_quiet++; // -n (implemented with a counter instead)
    14551527    while (opt_e) { // -e
    14561528        add_cmd_block(llist_pop(&opt_e));
     
    14591531        char *line;
    14601532        FILE *cmdfile;
    1461         cmdfile = xfopen_for_read(llist_pop(&opt_f));
     1533        cmdfile = xfopen_stdin(llist_pop(&opt_f));
    14621534        while ((line = xmalloc_fgetline(cmdfile)) != NULL) {
    14631535            add_cmd(line);
    14641536            free(line);
    14651537        }
    1466         fclose(cmdfile);
     1538        fclose_if_not_stdin(cmdfile);
    14671539    }
    14681540    /* if we didn't get a pattern from -e or -f, use argv[0] */
    1469     if (!(opt & 0x18)) {
     1541    if (!(opt & 0x30)) {
    14701542        if (!*argv)
    14711543            bb_show_usage();
     
    14811553     * files were specified or '-' was specified, take input from stdin.
    14821554     * Otherwise, we process all the files specified. */
    1483     if (argv[0] == NULL) {
     1555    G.input_file_list = argv;
     1556    if (!argv[0]) {
    14841557        if (opt & OPT_in_place)
    14851558            bb_error_msg_and_die(bb_msg_requires_arg, "-i");
    1486         add_input_file(stdin);
     1559        argv[0] = (char*)bb_msg_standard_input;
     1560        /* G.last_input_file = 0; - already is */
    14871561    } else {
    1488         int i;
    1489 
    1490         for (i = 0; argv[i]; i++) {
     1562        goto start;
     1563
     1564        for (; *argv; argv++) {
    14911565            struct stat statbuf;
    14921566            int nonstdoutfd;
    1493             FILE *file;
    14941567            sed_cmd_t *sed_cmd;
    14951568
    1496             if (LONE_DASH(argv[i]) && !(opt & OPT_in_place)) {
    1497                 add_input_file(stdin);
    1498                 process_files();
     1569            G.last_input_file++;
     1570 start:
     1571            if (!(opt & OPT_in_place)) {
     1572                if (LONE_DASH(*argv)) {
     1573                    *argv = (char*)bb_msg_standard_input;
     1574                    process_files();
     1575                }
    14991576                continue;
    15001577            }
    1501             file = fopen_or_warn(argv[i], "r");
    1502             if (!file) {
    1503                 status = EXIT_FAILURE;
     1578
     1579            /* -i: process each FILE separately: */
     1580
     1581            if (stat(*argv, &statbuf) != 0) {
     1582                bb_simple_perror_msg(*argv);
     1583                G.exitcode = EXIT_FAILURE;
     1584                G.current_input_file++;
    15041585                continue;
    15051586            }
    1506             add_input_file(file);
    1507             if (!(opt & OPT_in_place)) {
    1508                 continue;
    1509             }
    1510 
    1511             /* -i: process each FILE separately: */
    1512 
    1513             G.outname = xasprintf("%sXXXXXX", argv[i]);
     1587            G.outname = xasprintf("%sXXXXXX", *argv);
    15141588            nonstdoutfd = xmkstemp(G.outname);
    15151589            G.nonstdout = xfdopen_for_write(nonstdoutfd);
    1516 
    15171590            /* Set permissions/owner of output file */
    1518             fstat(fileno(file), &statbuf);
    15191591            /* chmod'ing AFTER chown would preserve suid/sgid bits,
    15201592             * but GNU sed 4.2.1 does not preserve them either */
     
    15271599
    15281600            if (opt_i) {
    1529                 char *backupname = xasprintf("%s%s", argv[i], opt_i);
    1530                 xrename(argv[i], backupname);
     1601                char *backupname = xasprintf("%s%s", *argv, opt_i);
     1602                xrename(*argv, backupname);
    15311603                free(backupname);
    15321604            }
    1533             /* else unlink(argv[i]); - rename below does this */
    1534             xrename(G.outname, argv[i]); //TODO: rollback backup on error?
     1605            /* else unlink(*argv); - rename below does this */
     1606            xrename(G.outname, *argv); //TODO: rollback backup on error?
    15351607            free(G.outname);
    15361608            G.outname = NULL;
    15371609
    1538             /* Re-enable disabled range matches */
     1610            /* Fix disabled range matches and mangled ",+N" ranges */
    15391611            for (sed_cmd = G.sed_cmd_head; sed_cmd; sed_cmd = sed_cmd->next) {
    15401612                sed_cmd->beg_line = sed_cmd->beg_line_orig;
     1613                sed_cmd->end_line = sed_cmd->end_line_orig;
    15411614            }
    15421615        }
    15431616        /* Here, to handle "sed 'cmds' nonexistent_file" case we did:
    1544          * if (G.current_input_file >= G.input_file_count)
    1545          *  return status;
     1617         * if (G.current_input_file[G.current_input_file] == NULL)
     1618         *  return G.exitcode;
    15461619         * but it's not needed since process_files() works correctly
    15471620         * in this case too. */
    15481621    }
     1622
    15491623    process_files();
    15501624
    1551     return status;
    1552 }
     1625    return G.exitcode;
     1626}
  • branches/3.3/mindi-busybox/editors/vi.c

    r3232 r3621  
    1818 *  More intelligence in refresh()
    1919 *  ":r !cmd"  and  "!cmd"  to filter text through an external command
    20  *  A true "undo" facility
    2120 *  An "ex" line oriented mode- maybe using "cmdedit"
    2221 */
     
    137136//config:
    138137//config:     This is not clean but helps a lot on serial lines and such.
     138//config:config FEATURE_VI_UNDO
     139//config:   bool "Support undo command 'u'"
     140//config:   default y
     141//config:   depends on VI
     142//config:   help
     143//config:     Support the 'u' command to undo insertion, deletion, and replacement
     144//config:     of text.
     145//config:config FEATURE_VI_UNDO_QUEUE
     146//config:   bool "Enable undo operation queuing"
     147//config:   default y
     148//config:   depends on FEATURE_VI_UNDO
     149//config:   help
     150//config:     The vi undo functions can use an intermediate queue to greatly lower
     151//config:     malloc() calls and overhead. When the maximum size of this queue is
     152//config:     reached, the contents of the queue are committed to the undo stack.
     153//config:     This increases the size of the undo code and allows some undo
     154//config:     operations (especially un-typing/backspacing) to be far more useful.
     155//config:config FEATURE_VI_UNDO_QUEUE_MAX
     156//config:   int "Maximum undo character queue size"
     157//config:   default 256
     158//config:   range 32 65536
     159//config:   depends on FEATURE_VI_UNDO_QUEUE
     160//config:   help
     161//config:     This option sets the number of bytes used at runtime for the queue.
     162//config:     Smaller values will create more undo objects and reduce the amount
     163//config:     of typed or backspaced characters that are grouped into one undo
     164//config:     operation; larger values increase the potential size of each undo
     165//config:     and will generally malloc() larger objects and less frequently.
     166//config:     Unless you want more (or less) frequent "undo points" while typing,
     167//config:     you should probably leave this unchanged.
    139168
    140169//applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
     
    223252// vda: removed "aAiIs" as they switch us into insert mode
    224253// and remembering input for replay after them makes no sense
    225 static const char modifying_cmds[] = "cCdDJoOpPrRxX<>~";
     254static const char modifying_cmds[] ALIGN1 = "cCdDJoOpPrRxX<>~";
    226255#endif
    227256
     
    278307                             // [code audit says "can be 0, 1 or 2 only"]
    279308    smallint cmd_mode;       // 0=command  1=insert 2=replace
    280     int file_modified;       // buffer contents changed (counter, not flag!)
    281     int last_file_modified; // = -1;
     309    int modified_count;      // buffer contents changed if !0
     310    int last_modified_count; // = -1;
    282311    int save_argc;           // how many file names on cmd line
    283312    int cmdcnt;              // repetition count
     
    348377
    349378    char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
     379#if ENABLE_FEATURE_VI_UNDO
     380// undo_push() operations
     381#define UNDO_INS         0
     382#define UNDO_DEL         1
     383#define UNDO_INS_CHAIN   2
     384#define UNDO_DEL_CHAIN   3
     385// UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG
     386#define UNDO_QUEUED_FLAG 4
     387#define UNDO_INS_QUEUED  4
     388#define UNDO_DEL_QUEUED  5
     389#define UNDO_USE_SPOS   32
     390#define UNDO_EMPTY      64
     391// Pass-through flags for functions that can be undone
     392#define NO_UNDO          0
     393#define ALLOW_UNDO       1
     394#define ALLOW_UNDO_CHAIN 2
     395# if ENABLE_FEATURE_VI_UNDO_QUEUE
     396#define ALLOW_UNDO_QUEUED 3
     397    char undo_queue_state;
     398    int undo_q;
     399    char *undo_queue_spos;  // Start position of queued operation
     400    char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
     401# else
     402// If undo queuing disabled, don't invoke the missing queue logic
     403#define ALLOW_UNDO_QUEUED 1
     404# endif
     405
     406    struct undo_object {
     407        struct undo_object *prev;   // Linking back avoids list traversal (LIFO)
     408        int start;      // Offset where the data should be restored/deleted
     409        int length;     // total data size
     410        uint8_t u_type;     // 0=deleted, 1=inserted, 2=swapped
     411        char undo_text[1];  // text that was deleted (if deletion)
     412    } *undo_stack_tail;
     413#endif /* ENABLE_FEATURE_VI_UNDO */
    350414};
    351415#define G (*ptr_to_globals)
     
    359423#define editing                 (G.editing            )
    360424#define cmd_mode                (G.cmd_mode           )
    361 #define file_modified           (G.file_modified      )
    362 #define last_file_modified      (G.last_file_modified )
     425#define modified_count          (G.modified_count     )
     426#define last_modified_count     (G.last_modified_count)
    363427#define save_argc               (G.save_argc          )
    364428#define cmdcnt                  (G.cmdcnt             )
     
    409473#define get_input_line__buf (G.get_input_line__buf)
    410474
     475#if ENABLE_FEATURE_VI_UNDO
     476#define undo_stack_tail  (G.undo_stack_tail )
     477# if ENABLE_FEATURE_VI_UNDO_QUEUE
     478#define undo_queue_state (G.undo_queue_state)
     479#define undo_q           (G.undo_q          )
     480#define undo_queue       (G.undo_queue      )
     481#define undo_queue_spos  (G.undo_queue_spos )
     482# endif
     483#endif
     484
    411485#define INIT_G() do { \
    412486    SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
    413     last_file_modified = -1; \
     487    last_modified_count = -1; \
    414488    /* "" but has space for 2 chars: */ \
    415489    IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
     
    417491
    418492
    419 static int init_text_buffer(char *); // init from file or create new
    420493static void edit_file(char *);  // edit one file
    421494static void do_cmd(int);    // execute a command
     
    428501static char *end_screen(void);  // get pointer to last char on screen
    429502static int count_lines(char *, char *); // count line from start to stop
    430 static char *find_line(int);    // find begining of line #li
     503static char *find_line(int);    // find beginning of line #li
    431504static char *move_to_col(char *, int);  // move "p" to column l
    432505static void dot_left(void); // move dot left- dont leave line
     
    438511static void dot_scroll(int, int);   // move the screen up or down
    439512static void dot_skip_over_ws(void); // move dot pat WS
    440 static void dot_delete(void);   // delete the char at 'dot'
    441513static char *bound_dot(char *); // make sure  text[0] <= P < "end"
    442514static char *new_screen(int, int);  // malloc virtual screen memory
    443 static char *char_insert(char *, char); // insert the char c at 'p'
     515#if !ENABLE_FEATURE_VI_UNDO
     516#define char_insert(a,b,c) char_insert(a,b)
     517#endif
     518static char *char_insert(char *, char, int);    // insert the char c at 'p'
    444519// might reallocate text[]! use p += stupid_insert(p, ...),
    445520// and be careful to not use pointers into potentially freed text[]!
     
    449524static char *skip_thing(char *, int, int, int); // skip some object
    450525static char *find_pair(char *, char);   // find matching pair ()  []  {}
    451 static char *text_hole_delete(char *, char *);  // at "p", delete a 'size' byte hole
     526#if !ENABLE_FEATURE_VI_UNDO
     527#define text_hole_delete(a,b,c) text_hole_delete(a,b)
     528#endif
     529static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole
    452530// might reallocate text[]! use p += text_hole_make(p, ...),
    453531// and be careful to not use pointers into potentially freed text[]!
    454532static uintptr_t text_hole_make(char *, int);   // at "p", make a 'size' byte hole
    455 static char *yank_delete(char *, char *, int, int); // yank text[] into register then delete
     533#if !ENABLE_FEATURE_VI_UNDO
     534#define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
     535#endif
     536static char *yank_delete(char *, char *, int, int, int);    // yank text[] into register then delete
    456537static void show_help(void);    // display some help info
    457538static void rawmode(void);  // set "raw" mode on tty
     
    461542static int readit(void);    // read (maybe cursor) key from stdin
    462543static int get_one_char(void);  // read 1 char from stdin
    463 static int file_size(const char *);   // what is the byte size of "fn"
    464 #if !ENABLE_FEATURE_VI_READONLY
    465 #define file_insert(fn, p, update_ro_status) file_insert(fn, p)
    466 #endif
    467544// file_insert might reallocate text[]!
    468545static int file_insert(const char *, char *, int);
     
    479556static void status_line(const char *, ...);     // print to status buf
    480557static void status_line_bold(const char *, ...);
     558static void status_line_bold_errno(const char *fn);
    481559static void not_implemented(const char *); // display "Not implemented" message
    482560static int format_edit_status(void);    // format file status on status line
     
    485563static void refresh(int);   // update the terminal from screen[]
    486564
    487 static void Indicate_Error(void);       // use flash or beep to indicate error
    488 #define indicate_error(c) Indicate_Error()
     565static void indicate_error(void);       // use flash or beep to indicate error
    489566static void Hit_Return(void);
    490567
     
    495572static char *get_one_address(char *, int *);    // get colon addr, if present
    496573static char *get_address(char *, int *, int *); // get two colon addrs, if present
     574#endif
    497575static void colon(char *);  // execute the "colon" mode cmds
    498 #endif
    499576#if ENABLE_FEATURE_VI_USE_SIGNALS
    500577static void winch_sig(int); // catch window size changes
     
    514591// might reallocate text[]! use p += string_insert(p, ...),
    515592// and be careful to not use pointers into potentially freed text[]!
    516 static uintptr_t string_insert(char *, const char *);   // insert the string at 'p'
     593# if !ENABLE_FEATURE_VI_UNDO
     594#define string_insert(a,b,c) string_insert(a,b)
     595# endif
     596static uintptr_t string_insert(char *, const char *, int);  // insert the string at 'p'
    517597#endif
    518598#if ENABLE_FEATURE_VI_YANKMARK
     
    521601static void check_context(char);    // remember context for '' command
    522602#endif
     603#if ENABLE_FEATURE_VI_UNDO
     604static void flush_undo_data(void);
     605static void undo_push(char *, unsigned int, unsigned char); // Push an operation on the undo stack
     606static void undo_pop(void); // Undo the last operation
     607# if ENABLE_FEATURE_VI_UNDO_QUEUE
     608static void undo_queue_commit(void);    // Flush any queued objects to the undo stack
     609# else
     610# define undo_queue_commit() ((void)0)
     611# endif
     612#else
     613#define flush_undo_data()   ((void)0)
     614#define undo_queue_commit() ((void)0)
     615#endif
     616
    523617#if ENABLE_FEATURE_VI_CRASHME
    524618static void crash_dummy();
     
    527621#endif
    528622
    529 
    530623static void write1(const char *out)
    531624{
     
    539632
    540633    INIT_G();
     634
     635#if ENABLE_FEATURE_VI_UNDO
     636    /* undo_stack_tail = NULL; - already is */
     637#if ENABLE_FEATURE_VI_UNDO_QUEUE
     638    undo_queue_state = UNDO_EMPTY;
     639    /* undo_q = 0; - already is  */
     640#endif
     641#endif
    541642
    542643#if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
     
    618719{
    619720    int rc;
    620     int size = file_size(fn);   // file size. -1 means does not exist.
     721
     722    flush_undo_data();
     723    modified_count = 0;
     724    last_modified_count = -1;
     725#if ENABLE_FEATURE_VI_YANKMARK
     726    /* init the marks */
     727    memset(mark, 0, sizeof(mark));
     728#endif
    621729
    622730    /* allocate/reallocate text buffer */
    623731    free(text);
    624     text_size = size + 10240;
     732    text_size = 10240;
    625733    screenbegin = dot = end = text = xzalloc(text_size);
    626734
     
    629737        current_filename = xstrdup(fn);
    630738    }
    631     if (size < 0) {
    632         // file dont exist. Start empty buf with dummy line
    633         char_insert(text, '\n');
    634         rc = 0;
    635     } else {
    636         rc = file_insert(fn, text, 1);
    637     }
    638     file_modified = 0;
    639     last_file_modified = -1;
    640 #if ENABLE_FEATURE_VI_YANKMARK
    641     /* init the marks. */
    642     memset(mark, 0, sizeof(mark));
    643 #endif
     739    rc = file_insert(fn, text, 1);
     740    if (rc < 0) {
     741        // file doesnt exist. Start empty buf with dummy line
     742        char_insert(text, '\n', NO_UNDO);
     743    }
    644744    return rc;
    645745}
     
    756856            } else {
    757857                crashme = 0;
    758                 string_insert(text, "\n\n#####  Ran out of text to work on.  #####\n\n"); // insert the string
     858                string_insert(text, "\n\n#####  Ran out of text to work on.  #####\n\n", NO_UNDO); // insert the string
    759859                dot = text;
    760860                refresh(FALSE);
     
    9091009#endif
    9101010
     1011#endif /* FEATURE_VI_COLON */
     1012
    9111013// buf must be no longer than MAX_INPUT_LEN!
    9121014static void colon(char *buf)
    9131015{
     1016#if !ENABLE_FEATURE_VI_COLON
     1017    /* Simple ":cmd" handler with minimal set of commands */
     1018    char *p = buf;
     1019    int cnt;
     1020
     1021    if (*p == ':')
     1022        p++;
     1023    cnt = strlen(p);
     1024    if (cnt == 0)
     1025        return;
     1026    if (strncmp(p, "quit", cnt) == 0
     1027     || strncmp(p, "q!", cnt) == 0
     1028    ) {
     1029        if (modified_count && p[1] != '!') {
     1030            status_line_bold("No write since last change (:%s! overrides)", p);
     1031        } else {
     1032            editing = 0;
     1033        }
     1034        return;
     1035    }
     1036    if (strncmp(p, "write", cnt) == 0
     1037     || strncmp(p, "wq", cnt) == 0
     1038     || strncmp(p, "wn", cnt) == 0
     1039     || (p[0] == 'x' && !p[1])
     1040    ) {
     1041        cnt = file_write(current_filename, text, end - 1);
     1042        if (cnt < 0) {
     1043            if (cnt == -1)
     1044                status_line_bold("Write error: %s", strerror(errno));
     1045        } else {
     1046            modified_count = 0;
     1047            last_modified_count = -1;
     1048            status_line("'%s' %dL, %dC",
     1049                current_filename,
     1050                count_lines(text, end - 1), cnt
     1051            );
     1052            if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
     1053             || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
     1054            ) {
     1055                editing = 0;
     1056            }
     1057        }
     1058        return;
     1059    }
     1060    if (strncmp(p, "file", cnt) == 0) {
     1061        last_status_cksum = 0;  // force status update
     1062        return;
     1063    }
     1064    if (sscanf(p, "%d", &cnt) > 0) {
     1065        dot = find_line(cnt);
     1066        dot_skip_over_ws();
     1067        return;
     1068    }
     1069    not_implemented(p);
     1070#else
     1071
    9141072    char c, *orig_buf, *buf1, *q, *r;
    9151073    char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
    916     int i, l, li, ch, b, e;
    917     int useforce, forced = FALSE;
     1074    int i, l, li, b, e;
     1075    int useforce;
    9181076
    9191077    // :3154    // if (-e line 3154) goto it  else stay put
     
    9371095        buf++;          // move past the ':'
    9381096
    939     li = ch = i = 0;
     1097    li = i = 0;
    9401098    b = e = -1;
    9411099    q = text;           // assume 1,$ for the range
     
    10151173            r = end_line(dot);
    10161174        }
    1017         dot = yank_delete(q, r, 1, YANKDEL);    // save, then delete lines
     1175        dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO);    // save, then delete lines
    10181176        dot_skip_over_ws();
    10191177    } else if (strncmp(cmd, "edit", i) == 0) {  // Edit a file
     1178        int size;
     1179
    10201180        // don't edit, if the current file has been modified
    1021         if (file_modified && !useforce) {
     1181        if (modified_count && !useforce) {
    10221182            status_line_bold("No write since last change (:%s! overrides)", cmd);
    10231183            goto ret;
     
    10351195        }
    10361196
    1037         if (init_text_buffer(fn) < 0)
    1038             goto ret;
     1197        size = init_text_buffer(fn);
    10391198
    10401199#if ENABLE_FEATURE_VI_YANKMARK
     
    10501209        // how many lines in text[]?
    10511210        li = count_lines(text, end - 1);
    1052         status_line("\"%s\"%s"
     1211        status_line("'%s'%s"
    10531212            IF_FEATURE_VI_READONLY("%s")
    1054             " %dL, %dC", current_filename,
    1055             (file_size(fn) < 0 ? " [New file]" : ""),
     1213            " %dL, %dC",
     1214            current_filename,
     1215            (size < 0 ? " [New file]" : ""),
    10561216            IF_FEATURE_VI_READONLY(
    10571217                ((readonly_mode) ? " [Readonly]" : ""),
    10581218            )
    1059             li, ch);
     1219            li, (int)(end - text)
     1220        );
    10601221    } else if (strncmp(cmd, "file", i) == 0) {  // what File is this
    10611222        if (b != -1 || e != -1) {
     
    11221283        }
    11231284        // don't exit if the file been modified
    1124         if (file_modified) {
     1285        if (modified_count) {
    11251286            status_line_bold("No write since last change (:%s! overrides)", cmd);
    11261287            goto ret;
     
    11461307        editing = 0;
    11471308    } else if (strncmp(cmd, "read", i) == 0) {  // read file into text[]
     1309        int size;
     1310
    11481311        fn = args;
    11491312        if (!fn[0]) {
     
    11551318        }
    11561319        // read after current line- unless user said ":0r foo"
    1157         if (b != 0)
     1320        if (b != 0) {
    11581321            q = next_line(q);
     1322            // read after last line
     1323            if (q == end-1)
     1324                ++q;
     1325        }
    11591326        { // dance around potentially-reallocated text[]
    11601327            uintptr_t ofs = q - text;
    1161             ch = file_insert(fn, q, 0);
     1328            size = file_insert(fn, q, 0);
    11621329            q = text + ofs;
    11631330        }
    1164         if (ch < 0)
     1331        if (size < 0)
    11651332            goto ret;   // nothing was inserted
    11661333        // how many lines in text[]?
    1167         li = count_lines(q, q + ch - 1);
    1168         status_line("\"%s\""
     1334        li = count_lines(q, q + size - 1);
     1335        status_line("'%s'"
    11691336            IF_FEATURE_VI_READONLY("%s")
    1170             " %dL, %dC", fn,
     1337            " %dL, %dC",
     1338            fn,
    11711339            IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
    1172             li, ch);
    1173         if (ch > 0) {
     1340            li, size
     1341        );
     1342        if (size > 0) {
    11741343            // if the insert is before "dot" then we need to update
    11751344            if (q <= dot)
    1176                 dot += ch;
    1177             /*file_modified++; - done by file_insert */
     1345                dot += size;
    11781346        }
    11791347    } else if (strncmp(cmd, "rewind", i) == 0) {    // rewind cmd line args
    1180         if (file_modified && !useforce) {
     1348        if (modified_count && !useforce) {
    11811349            status_line_bold("No write since last change (:%s! overrides)", cmd);
    11821350        } else {
     
    12351403        size_t len_F, len_R;
    12361404        int gflag;      // global replace flag
     1405#if ENABLE_FEATURE_VI_UNDO
     1406        int dont_chain_first_item = ALLOW_UNDO;
     1407#endif
    12371408
    12381409        // F points to the "find" pattern
     
    12691440                uintptr_t bias;
    12701441                // we found the "find" pattern - delete it
    1271                 text_hole_delete(found, found + len_F - 1);
    1272                 // inset the "replace" patern
    1273                 bias = string_insert(found, R); // insert the string
     1442                // For undo support, the first item should not be chained
     1443                text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
     1444#if ENABLE_FEATURE_VI_UNDO
     1445                dont_chain_first_item = ALLOW_UNDO_CHAIN;
     1446#endif
     1447                // insert the "replace" patern
     1448                bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
    12741449                found += bias;
    12751450                ls += bias;
     
    12931468            || (cmd[0] == 'x' && !cmd[1])
    12941469    ) {
     1470        int size;
     1471        //int forced = FALSE;
     1472
    12951473        // is there a file name to write to?
    12961474        if (args[0]) {
     
    12991477#if ENABLE_FEATURE_VI_READONLY
    13001478        if (readonly_mode && !useforce) {
    1301             status_line_bold("\"%s\" File is read only", fn);
     1479            status_line_bold("'%s' is read only", fn);
    13021480            goto ret;
    13031481        }
     
    13051483        // how many lines in text[]?
    13061484        li = count_lines(q, r);
    1307         ch = r - q + 1;
    1308         // see if file exists- if not, its just a new file request
    1309         if (useforce) {
     1485        size = r - q + 1;
     1486        //if (useforce) {
    13101487            // if "fn" is not write-able, chmod u+w
    13111488            // sprintf(syscmd, "chmod u+w %s", fn);
    13121489            // system(syscmd);
    1313             forced = TRUE;
    1314         }
     1490            // forced = TRUE;
     1491        //}
    13151492        l = file_write(fn, q, r);
    1316         if (useforce && forced) {
     1493        //if (useforce && forced) {
    13171494            // chmod u-w
    13181495            // sprintf(syscmd, "chmod u-w %s", fn);
    13191496            // system(syscmd);
    1320             forced = FALSE;
    1321         }
     1497            // forced = FALSE;
     1498        //}
    13221499        if (l < 0) {
    13231500            if (l == -1)
    1324                 status_line_bold("\"%s\" %s", fn, strerror(errno));
     1501                status_line_bold_errno(fn);
    13251502        } else {
    1326             status_line("\"%s\" %dL, %dC", fn, li, l);
    1327             if (q == text && r == end - 1 && l == ch) {
    1328                 file_modified = 0;
    1329                 last_file_modified = -1;
     1503            status_line("'%s' %dL, %dC", fn, li, l);
     1504            if (q == text && r == end - 1 && l == size) {
     1505                modified_count = 0;
     1506                last_modified_count = -1;
    13301507            }
    13311508            if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n'
    13321509                || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N'
    13331510                )
    1334              && l == ch
     1511             && l == size
    13351512            ) {
    13361513                editing = 0;
     
    13591536    status_line(":s expression missing delimiters");
    13601537#endif
    1361 }
    1362 
    13631538#endif /* FEATURE_VI_COLON */
     1539}
    13641540
    13651541static void Hit_Return(void)
     
    15081684static char *prev_line(char *p) // return pointer first char prev line
    15091685{
    1510     p = begin_line(p);  // goto begining of cur line
     1686    p = begin_line(p);  // goto beginning of cur line
    15111687    if (p > text && p[-1] == '\n')
    15121688        p--;            // step to prev line
    1513     p = begin_line(p);  // goto begining of prev line
     1689    p = begin_line(p);  // goto beginning of prev line
    15141690    return p;
    15151691}
     
    15591735}
    15601736
    1561 static char *find_line(int li)  // find begining of line #li
     1737static char *find_line(int li)  // find beginning of line #li
    15621738{
    15631739    char *q;
     
    15721748static void dot_left(void)
    15731749{
     1750    undo_queue_commit();
    15741751    if (dot > text && dot[-1] != '\n')
    15751752        dot--;
     
    15781755static void dot_right(void)
    15791756{
     1757    undo_queue_commit();
    15801758    if (dot < end - 1 && *dot != '\n')
    15811759        dot++;
     
    15841762static void dot_begin(void)
    15851763{
     1764    undo_queue_commit();
    15861765    dot = begin_line(dot);  // return pointer to first char cur line
    15871766}
     
    15891768static void dot_end(void)
    15901769{
     1770    undo_queue_commit();
    15911771    dot = end_line(dot);    // return pointer to last char cur line
    15921772}
     
    16141794static void dot_next(void)
    16151795{
     1796    undo_queue_commit();
    16161797    dot = next_line(dot);
    16171798}
     
    16191800static void dot_prev(void)
    16201801{
     1802    undo_queue_commit();
    16211803    dot = prev_line(dot);
    16221804}
     
    16261808    char *q;
    16271809
     1810    undo_queue_commit();
    16281811    for (; cnt > 0; cnt--) {
    16291812        if (dir < 0) {
     
    16531836}
    16541837
    1655 static void dot_delete(void)    // delete the char at 'dot'
    1656 {
    1657     text_hole_delete(dot, dot);
    1658 }
    1659 
    16601838static char *bound_dot(char *p) // make sure  text[0] <= P < "end"
    16611839{
    16621840    if (p >= end && end > text) {
    16631841        p = end - 1;
    1664         indicate_error('1');
     1842        indicate_error();
    16651843    }
    16661844    if (p < text) {
    16671845        p = text;
    1668         indicate_error('2');
     1846        indicate_error();
    16691847    }
    16701848    return p;
     
    17071885static char *char_search(char *p, const char *pat, int dir, int range)
    17081886{
     1887    struct re_pattern_buffer preg;
     1888    const char *err;
    17091889    char *q;
    1710     struct re_pattern_buffer preg;
    17111890    int i;
    17121891    int size;
    17131892
    17141893    re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
    1715     preg.translate = 0;
    1716     preg.fastmap = 0;
    1717     preg.buffer = 0;
    1718     preg.allocated = 0;
     1894    if (ignorecase)
     1895        re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
     1896
     1897    memset(&preg, 0, sizeof(preg));
     1898    err = re_compile_pattern(pat, strlen(pat), &preg);
     1899    if (err != NULL) {
     1900        status_line_bold("bad search pattern '%s': %s", pat, err);
     1901        return p;
     1902    }
    17191903
    17201904    // assume a LIMITED forward search
    1721     q = next_line(p);
    1722     q = end_line(q);
    17231905    q = end - 1;
    1724     if (dir == BACK) {
    1725         q = prev_line(p);
     1906    if (dir == BACK)
    17261907        q = text;
    1727     }
    1728     // count the number of chars to search over, forward or backward
    1729     size = q - p;
    1730     if (size < 0)
    1731         size = p - q;
    17321908    // RANGE could be negative if we are searching backwards
    17331909    range = q - p;
    1734 
    1735     q = (char *)re_compile_pattern(pat, strlen(pat), (struct re_pattern_buffer *)&preg);
    1736     if (q != 0) {
    1737         // The pattern was not compiled
    1738         status_line_bold("bad search pattern: \"%s\": %s", pat, q);
    1739         i = 0;          // return p if pattern not compiled
    1740         goto cs1;
    1741     }
    1742 
    17431910    q = p;
     1911    size = range;
    17441912    if (range < 0) {
     1913        size = -size;
    17451914        q = p - size;
    17461915        if (q < text)
     
    17481917    }
    17491918    // search for the compiled pattern, preg, in p[]
    1750     // range < 0- search backward
    1751     // range > 0- search forward
     1919    // range < 0: search backward
     1920    // range > 0: search forward
    17521921    // 0 < start < size
    1753     // re_search() < 0  not found or error
    1754     // re_search() > 0  index of found pattern
    1755     //            struct pattern    char     int    int    int     struct reg
    1756     // re_search (*pattern_buffer,  *string, size,  start, range,  *regs)
    1757     i = re_search(&preg, q, size, 0, range, 0);
    1758     if (i == -1) {
    1759         p = 0;
    1760         i = 0;          // return NULL if pattern not found
    1761     }
    1762  cs1:
    1763     if (dir == FORWARD) {
     1922    // re_search() < 0: not found or error
     1923    // re_search() >= 0: index of found pattern
     1924    //           struct pattern   char     int   int    int    struct reg
     1925    // re_search(*pattern_buffer, *string, size, start, range, *regs)
     1926    i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
     1927    regfree(&preg);
     1928    if (i < 0)
     1929        return NULL;
     1930    if (dir == FORWARD)
    17641931        p = p + i;
    1765     } else {
     1932    else
    17661933        p = p - i;
    1767     }
    17681934    return p;
    17691935}
     
    17901956    len = strlen(pat);
    17911957    if (dir == FORWARD) {
    1792         stop = end - 1; // assume range is p - end-1
     1958        stop = end - 1; // assume range is p..end-1
    17931959        if (range == LIMITED)
    17941960            stop = next_line(p);    // range is to next line
     
    17991965        }
    18001966    } else if (dir == BACK) {
    1801         stop = text;    // assume range is text - p
     1967        stop = text;    // assume range is text..p
    18021968        if (range == LIMITED)
    18031969            stop = prev_line(p);    // range is to prev line
     
    18161982#endif /* FEATURE_VI_SEARCH */
    18171983
    1818 static char *char_insert(char *p, char c) // insert the char c at 'p'
     1984static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
    18191985{
    18201986    if (c == 22) {      // Is this an ctrl-V?
     
    18231989        c = get_one_char();
    18241990        *p = c;
     1991#if ENABLE_FEATURE_VI_UNDO
     1992        switch (undo) {
     1993            case ALLOW_UNDO:
     1994                undo_push(p, 1, UNDO_INS);
     1995                break;
     1996            case ALLOW_UNDO_CHAIN:
     1997                undo_push(p, 1, UNDO_INS_CHAIN);
     1998                break;
     1999# if ENABLE_FEATURE_VI_UNDO_QUEUE
     2000            case ALLOW_UNDO_QUEUED:
     2001                undo_push(p, 1, UNDO_INS_QUEUED);
     2002                break;
     2003# endif
     2004        }
     2005#else
     2006        modified_count++;
     2007#endif /* ENABLE_FEATURE_VI_UNDO */
    18252008        p++;
    1826         file_modified++;
    18272009    } else if (c == 27) {   // Is this an ESC?
    18282010        cmd_mode = 0;
     2011        undo_queue_commit();
    18292012        cmdcnt = 0;
    18302013        end_cmd_q();    // stop adding to q
     
    18342017        }
    18352018    } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
    1836         //     123456789
    1837         if ((p[-1] != '\n') && (dot>text)) {
     2019        if (p > text) {
    18382020            p--;
    1839             p = text_hole_delete(p, p); // shrink buffer 1 char
     2021            p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED);  // shrink buffer 1 char
    18402022        }
    18412023    } else {
    1842 #if ENABLE_FEATURE_VI_SETOPTS
    18432024        // insert a char into text[]
    1844         char *sp;       // "save p"
    1845 #endif
    1846 
    18472025        if (c == 13)
    18482026            c = '\n';   // translate \r to \n
    1849 #if ENABLE_FEATURE_VI_SETOPTS
    1850         sp = p;         // remember addr of insert
    1851 #endif
     2027#if ENABLE_FEATURE_VI_UNDO
     2028# if ENABLE_FEATURE_VI_UNDO_QUEUE
     2029        if (c == '\n')
     2030            undo_queue_commit();
     2031# endif
     2032        switch (undo) {
     2033            case ALLOW_UNDO:
     2034                undo_push(p, 1, UNDO_INS);
     2035                break;
     2036            case ALLOW_UNDO_CHAIN:
     2037                undo_push(p, 1, UNDO_INS_CHAIN);
     2038                break;
     2039# if ENABLE_FEATURE_VI_UNDO_QUEUE
     2040            case ALLOW_UNDO_QUEUED:
     2041                undo_push(p, 1, UNDO_INS_QUEUED);
     2042                break;
     2043# endif
     2044        }
     2045#else
     2046        modified_count++;
     2047#endif /* ENABLE_FEATURE_VI_UNDO */
    18522048        p += 1 + stupid_insert(p, c);   // insert the char
    18532049#if ENABLE_FEATURE_VI_SETOPTS
    1854         if (showmatch && strchr(")]}", *sp) != NULL) {
    1855             showmatching(sp);
     2050        if (showmatch && strchr(")]}", c) != NULL) {
     2051            showmatching(p - 1);
    18562052        }
    18572053        if (autoindent && c == '\n') {  // auto indent the new line
     
    18652061                p += bias;
    18662062                q += bias;
     2063#if ENABLE_FEATURE_VI_UNDO
     2064                undo_push(p, len, UNDO_INS);
     2065#endif
    18672066                memcpy(p, q, len);
    18682067                p += len;
     
    18822081    p += bias;
    18832082    *p = c;
    1884     //file_modified++; - done by text_hole_make()
    18852083    return bias;
    18862084}
     
    20112209
    20122210// find matching char of pair  ()  []  {}
     2211// will crash if c is not one of these
    20132212static char *find_pair(char *p, const char c)
    20142213{
    2015     char match, *q;
     2214    const char *braces = "()[]{}";
     2215    char match;
    20162216    int dir, level;
    20172217
    2018     match = ')';
     2218    dir = strchr(braces, c) - braces;
     2219    dir ^= 1;
     2220    match = braces[dir];
     2221    dir = ((dir & 1) << 1) - 1; /* 1 for ([{, -1 for )\} */
     2222
     2223    // look for match, count levels of pairs  (( ))
    20192224    level = 1;
    2020     dir = 1;            // assume forward
    2021     switch (c) {
    2022     case '(': match = ')'; break;
    2023     case '[': match = ']'; break;
    2024     case '{': match = '}'; break;
    2025     case ')': match = '('; dir = -1; break;
    2026     case ']': match = '['; dir = -1; break;
    2027     case '}': match = '{'; dir = -1; break;
    2028     }
    2029     for (q = p + dir; text <= q && q < end; q += dir) {
    2030         // look for match, count levels of pairs  (( ))
    2031         if (*q == c)
     2225    for (;;) {
     2226        p += dir;
     2227        if (p < text || p >= end)
     2228            return NULL;
     2229        if (*p == c)
    20322230            level++;    // increase pair levels
    2033         if (*q == match)
     2231        if (*p == match) {
    20342232            level--;    // reduce pair level
    2035         if (level == 0)
    2036             break;      // found matching pair
    2037     }
    2038     if (level != 0)
    2039         q = NULL;       // indicate no match
    2040     return q;
     2233            if (level == 0)
     2234                return p; // found matching pair
     2235        }
     2236    }
    20412237}
    20422238
     
    20502246    q = find_pair(p, *p);   // get loc of matching char
    20512247    if (q == NULL) {
    2052         indicate_error('3');    // no matching char
     2248        indicate_error();   // no matching char
    20532249    } else {
    20542250        // "q" now points to matching pair
     
    20622258}
    20632259#endif /* FEATURE_VI_SETOPTS */
     2260
     2261#if ENABLE_FEATURE_VI_UNDO
     2262static void flush_undo_data(void)
     2263{
     2264    struct undo_object *undo_entry;
     2265
     2266    while (undo_stack_tail) {
     2267        undo_entry = undo_stack_tail;
     2268        undo_stack_tail = undo_entry->prev;
     2269        free(undo_entry);
     2270    }
     2271}
     2272
     2273// Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
     2274static void undo_push(char *src, unsigned int length, uint8_t u_type)   // Add to the undo stack
     2275{
     2276    struct undo_object *undo_entry;
     2277
     2278    // "u_type" values
     2279    // UNDO_INS: insertion, undo will remove from buffer
     2280    // UNDO_DEL: deleted text, undo will restore to buffer
     2281    // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
     2282    // The CHAIN operations are for handling multiple operations that the user
     2283    // performs with a single action, i.e. REPLACE mode or find-and-replace commands
     2284    // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
     2285    // for the INS/DEL operation. The raw values should be equal to the values of
     2286    // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
     2287
     2288#if ENABLE_FEATURE_VI_UNDO_QUEUE
     2289    // This undo queuing functionality groups multiple character typing or backspaces
     2290    // into a single large undo object. This greatly reduces calls to malloc() for
     2291    // single-character operations while typing and has the side benefit of letting
     2292    // an undo operation remove chunks of text rather than a single character.
     2293    switch (u_type) {
     2294    case UNDO_EMPTY:    // Just in case this ever happens...
     2295        return;
     2296    case UNDO_DEL_QUEUED:
     2297        if (length != 1)
     2298            return; // Only queue single characters
     2299        switch (undo_queue_state) {
     2300        case UNDO_EMPTY:
     2301            undo_queue_state = UNDO_DEL;
     2302        case UNDO_DEL:
     2303            undo_queue_spos = src;
     2304            undo_q++;
     2305            undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
     2306            // If queue is full, dump it into an object
     2307            if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
     2308                undo_queue_commit();
     2309            return;
     2310        case UNDO_INS:
     2311            // Switch from storing inserted text to deleted text
     2312            undo_queue_commit();
     2313            undo_push(src, length, UNDO_DEL_QUEUED);
     2314            return;
     2315        }
     2316        break;
     2317    case UNDO_INS_QUEUED:
     2318        if (length != 1)
     2319            return;
     2320        switch (undo_queue_state) {
     2321        case UNDO_EMPTY:
     2322            undo_queue_state = UNDO_INS;
     2323            undo_queue_spos = src;
     2324        case UNDO_INS:
     2325            undo_q++;   // Don't need to save any data for insertions
     2326            if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
     2327                undo_queue_commit();
     2328            return;
     2329        case UNDO_DEL:
     2330            // Switch from storing deleted text to inserted text
     2331            undo_queue_commit();
     2332            undo_push(src, length, UNDO_INS_QUEUED);
     2333            return;
     2334        }
     2335        break;
     2336    }
     2337#else
     2338    // If undo queuing is disabled, ignore the queuing flag entirely
     2339    u_type = u_type & ~UNDO_QUEUED_FLAG;
     2340#endif
     2341
     2342    // Allocate a new undo object
     2343    if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
     2344        // For UNDO_DEL objects, save deleted text
     2345        if ((src + length) == end)
     2346            length--;
     2347        // If this deletion empties text[], strip the newline. When the buffer becomes
     2348        // zero-length, a newline is added back, which requires this to compensate.
     2349        undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
     2350        memcpy(undo_entry->undo_text, src, length);
     2351    } else {
     2352        undo_entry = xzalloc(sizeof(*undo_entry));
     2353    }
     2354    undo_entry->length = length;
     2355#if ENABLE_FEATURE_VI_UNDO_QUEUE
     2356    if ((u_type & UNDO_USE_SPOS) != 0) {
     2357        undo_entry->start = undo_queue_spos - text; // use start position from queue
     2358    } else {
     2359        undo_entry->start = src - text; // use offset from start of text buffer
     2360    }
     2361    u_type = (u_type & ~UNDO_USE_SPOS);
     2362#else
     2363    undo_entry->start = src - text;
     2364#endif
     2365    undo_entry->u_type = u_type;
     2366
     2367    // Push it on undo stack
     2368    undo_entry->prev = undo_stack_tail;
     2369    undo_stack_tail = undo_entry;
     2370    modified_count++;
     2371}
     2372
     2373static void undo_pop(void)  // Undo the last operation
     2374{
     2375    int repeat;
     2376    char *u_start, *u_end;
     2377    struct undo_object *undo_entry;
     2378
     2379    // Commit pending undo queue before popping (should be unnecessary)
     2380    undo_queue_commit();
     2381
     2382    undo_entry = undo_stack_tail;
     2383    // Check for an empty undo stack
     2384    if (!undo_entry) {
     2385        status_line("Already at oldest change");
     2386        return;
     2387    }
     2388
     2389    switch (undo_entry->u_type) {
     2390    case UNDO_DEL:
     2391    case UNDO_DEL_CHAIN:
     2392        // make hole and put in text that was deleted; deallocate text
     2393        u_start = text + undo_entry->start;
     2394        text_hole_make(u_start, undo_entry->length);
     2395        memcpy(u_start, undo_entry->undo_text, undo_entry->length);
     2396        status_line("Undo [%d] %s %d chars at position %d",
     2397            modified_count, "restored",
     2398            undo_entry->length, undo_entry->start
     2399        );
     2400        break;
     2401    case UNDO_INS:
     2402    case UNDO_INS_CHAIN:
     2403        // delete what was inserted
     2404        u_start = undo_entry->start + text;
     2405        u_end = u_start - 1 + undo_entry->length;
     2406        text_hole_delete(u_start, u_end, NO_UNDO);
     2407        status_line("Undo [%d] %s %d chars at position %d",
     2408            modified_count, "deleted",
     2409            undo_entry->length, undo_entry->start
     2410        );
     2411        break;
     2412    }
     2413    repeat = 0;
     2414    switch (undo_entry->u_type) {
     2415    // If this is the end of a chain, lower modification count and refresh display
     2416    case UNDO_DEL:
     2417    case UNDO_INS:
     2418        dot = (text + undo_entry->start);
     2419        refresh(FALSE);
     2420        break;
     2421    case UNDO_DEL_CHAIN:
     2422    case UNDO_INS_CHAIN:
     2423        repeat = 1;
     2424        break;
     2425    }
     2426    // Deallocate the undo object we just processed
     2427    undo_stack_tail = undo_entry->prev;
     2428    free(undo_entry);
     2429    modified_count--;
     2430    // For chained operations, continue popping all the way down the chain.
     2431    if (repeat) {
     2432        undo_pop(); // Follow the undo chain if one exists
     2433    }
     2434}
     2435
     2436#if ENABLE_FEATURE_VI_UNDO_QUEUE
     2437static void undo_queue_commit(void) // Flush any queued objects to the undo stack
     2438{
     2439    // Pushes the queue object onto the undo stack
     2440    if (undo_q > 0) {
     2441        // Deleted character undo events grow from the end
     2442        undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
     2443            undo_q,
     2444            (undo_queue_state | UNDO_USE_SPOS)
     2445        );
     2446        undo_queue_state = UNDO_EMPTY;
     2447        undo_q = 0;
     2448    }
     2449}
     2450#endif
     2451
     2452#endif /* ENABLE_FEATURE_VI_UNDO */
    20642453
    20652454// open a hole in text[]
     
    20942483    memmove(p + size, p, end - size - p);
    20952484    memset(p, ' ', size);   // clear new hole
    2096     file_modified++;
    20972485    return bias;
    20982486}
    20992487
    21002488//  close a hole in text[]
    2101 static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclusive
     2489//  "undo" value indicates if this operation should be undo-able
     2490static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive
    21022491{
    21032492    char *src, *dest;
     
    21142503    hole_size = q - p + 1;
    21152504    cnt = end - src;
     2505#if ENABLE_FEATURE_VI_UNDO
     2506    switch (undo) {
     2507        case NO_UNDO:
     2508            break;
     2509        case ALLOW_UNDO:
     2510            undo_push(p, hole_size, UNDO_DEL);
     2511            break;
     2512        case ALLOW_UNDO_CHAIN:
     2513            undo_push(p, hole_size, UNDO_DEL_CHAIN);
     2514            break;
     2515# if ENABLE_FEATURE_VI_UNDO_QUEUE
     2516        case ALLOW_UNDO_QUEUED:
     2517            undo_push(p, hole_size, UNDO_DEL_QUEUED);
     2518            break;
     2519# endif
     2520    }
     2521    modified_count--;
     2522#endif
    21162523    if (src < text || src > end)
    21172524        goto thd0;
    21182525    if (dest < text || dest >= end)
    21192526        goto thd0;
     2527    modified_count++;
    21202528    if (src >= end)
    21212529        goto thd_atend; // just delete the end of the buffer
     
    21272535    if (end <= text)
    21282536        dest = end = text;  // keep pointers valid
    2129     file_modified++;
    21302537 thd0:
    21312538    return dest;
     
    21352542// if dist <= 0, do not include, or go past, a NewLine
    21362543//
    2137 static char *yank_delete(char *start, char *stop, int dist, int yf)
     2544static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
    21382545{
    21392546    char *p;
     
    21642571#endif
    21652572    if (yf == YANKDEL) {
    2166         p = text_hole_delete(start, stop);
     2573        p = text_hole_delete(start, stop, undo);
    21672574    }                   // delete lines
    21682575    return p;
     
    22302637// might reallocate text[]! use p += string_insert(p, ...),
    22312638// and be careful to not use pointers into potentially freed text[]!
    2232 static uintptr_t string_insert(char *p, const char *s) // insert the string at 'p'
     2639static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
    22332640{
    22342641    uintptr_t bias;
     
    22362643
    22372644    i = strlen(s);
     2645#if ENABLE_FEATURE_VI_UNDO
     2646    switch (undo) {
     2647        case ALLOW_UNDO:
     2648            undo_push(p, i, UNDO_INS);
     2649            break;
     2650        case ALLOW_UNDO_CHAIN:
     2651            undo_push(p, i, UNDO_INS_CHAIN);
     2652            break;
     2653    }
     2654#endif
    22382655    bias = text_hole_make(p, i);
    22392656    p += bias;
     
    23872804{
    23882805    struct pollfd pfd[1];
     2806
     2807    if (hund != 0)
     2808        fflush_all();
    23892809
    23902810    pfd[0].fd = STDIN_FILENO;
     
    24842904}
    24852905
    2486 static int file_size(const char *fn) // what is the byte size of "fn"
    2487 {
    2488     struct stat st_buf;
    2489     int cnt;
    2490 
    2491     cnt = -1;
    2492     if (fn && stat(fn, &st_buf) == 0)   // see if file exists
    2493         cnt = (int) st_buf.st_size;
    2494     return cnt;
    2495 }
    2496 
    24972906// might reallocate text[]!
    2498 static int file_insert(const char *fn, char *p, int update_ro_status)
     2907static int file_insert(const char *fn, char *p, int initial)
    24992908{
    25002909    int cnt = -1;
     
    25022911    struct stat statbuf;
    25032912
    2504     /* Validate file */
    2505     if (stat(fn, &statbuf) < 0) {
    2506         status_line_bold("\"%s\" %s", fn, strerror(errno));
    2507         goto fi0;
    2508     }
    2509     if (!S_ISREG(statbuf.st_mode)) {
    2510         // This is not a regular file
    2511         status_line_bold("\"%s\" Not a regular file", fn);
    2512         goto fi0;
    2513     }
    2514     if (p < text || p > end) {
    2515         status_line_bold("Trying to insert file outside of memory");
    2516         goto fi0;
    2517     }
    2518 
    2519     // read file to buffer
     2913    if (p < text)
     2914        p = text;
     2915    if (p > end)
     2916        p = end;
     2917
    25202918    fd = open(fn, O_RDONLY);
    25212919    if (fd < 0) {
    2522         status_line_bold("\"%s\" %s", fn, strerror(errno));
    2523         goto fi0;
    2524     }
    2525     size = statbuf.st_size;
     2920        if (!initial)
     2921            status_line_bold_errno(fn);
     2922        return cnt;
     2923    }
     2924
     2925    /* Validate file */
     2926    if (fstat(fd, &statbuf) < 0) {
     2927        status_line_bold_errno(fn);
     2928        goto fi;
     2929    }
     2930    if (!S_ISREG(statbuf.st_mode)) {
     2931        status_line_bold("'%s' is not a regular file", fn);
     2932        goto fi;
     2933    }
     2934    size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
    25262935    p += text_hole_make(p, size);
    2527     cnt = safe_read(fd, p, size);
     2936    cnt = full_read(fd, p, size);
    25282937    if (cnt < 0) {
    2529         status_line_bold("\"%s\" %s", fn, strerror(errno));
    2530         p = text_hole_delete(p, p + size - 1);  // un-do buffer insert
     2938        status_line_bold_errno(fn);
     2939        p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
    25312940    } else if (cnt < size) {
    2532         // There was a partial read, shrink unused space text[]
    2533         p = text_hole_delete(p + cnt, p + (size - cnt) - 1);    // un-do buffer insert
    2534         status_line_bold("can't read all of file \"%s\"", fn);
    2535     }
    2536     if (cnt >= size)
    2537         file_modified++;
     2941        // There was a partial read, shrink unused space
     2942        p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
     2943        status_line_bold("can't read '%s'", fn);
     2944    }
     2945 fi:
    25382946    close(fd);
    2539  fi0:
     2947
    25402948#if ENABLE_FEATURE_VI_READONLY
    2541     if (update_ro_status
     2949    if (initial
    25422950     && ((access(fn, W_OK) < 0) ||
    25432951        /* root will always have access()
     
    25722980    if (charcnt == cnt) {
    25732981        // good write
    2574         //file_modified = FALSE;
     2982        //modified_count = FALSE;
    25752983    } else {
    25762984        charcnt = 0;
     
    26453053}
    26463054
    2647 static void Indicate_Error(void)
     3055static void indicate_error(void)
    26483056{
    26493057#if ENABLE_FEATURE_VI_CRASHME
     
    27183126}
    27193127
     3128static void status_line_bold_errno(const char *fn)
     3129{
     3130    status_line_bold("'%s' %s", fn, strerror(errno));
     3131}
     3132
    27203133// format status buffer
    27213134static void status_line(const char *format, ...)
     
    27893202    int cur, percent, ret, trunc_at;
    27903203
    2791     // file_modified is now a counter rather than a flag.  this
     3204    // modified_count is now a counter rather than a flag.  this
    27923205    // helps reduce the amount of line counting we need to do.
    27933206    // (this will cause a mis-reporting of modified status
     
    27993212    cur = count_lines(text, dot);
    28003213
    2801     // reduce counting -- the total lines can't have
    2802     // changed if we haven't done any edits.
    2803     if (file_modified != last_file_modified) {
     3214    // count_lines() is expensive.
     3215    // Call it only if something was changed since last time
     3216    // we were here:
     3217    if (modified_count != last_modified_count) {
    28043218        tot = cur + count_lines(dot, end - 1) - 1;
    2805         last_file_modified = file_modified;
     3219        last_modified_count = modified_count;
    28063220    }
    28073221
     
    28303244        (readonly_mode ? " [Readonly]" : ""),
    28313245#endif
    2832         (file_modified ? " [Modified]" : ""),
     3246        (modified_count ? " [Modified]" : ""),
    28333247        cur, tot, percent);
    28343248
     
    29433357        }
    29443358
    2945         // see if there are any changes between vitual screen and out_buf
     3359        // see if there are any changes between virtual screen and out_buf
    29463360        changed = FALSE;    // assume no change
    29473361        cs = 0;
     
    29803394        if (ce > columns - 1) ce = columns - 1;
    29813395        if (cs > ce) { cs = 0; ce = columns - 1; }
    2982         // is there a change between vitual screen and out_buf
     3396        // is there a change between virtual screen and out_buf
    29833397        if (changed) {
    29843398            // copy changed part of buffer to virtual screen
     
    30553469            // don't Replace past E-o-l
    30563470            cmd_mode = 1;   // convert to insert
     3471            undo_queue_commit();
    30573472        } else {
    30583473            if (1 <= c || Isprint(c)) {
    30593474                if (c != 27)
    3060                     dot = yank_delete(dot, dot, 0, YANKDEL);    // delete char
    3061                 dot = char_insert(dot, c);  // insert new char
     3475                    dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO);    // delete char
     3476                dot = char_insert(dot, c, ALLOW_UNDO_CHAIN);    // insert new char
    30623477            }
    30633478            goto dc1;
     
    30693484        // insert the char c at "dot"
    30703485        if (1 <= c || Isprint(c)) {
    3071             dot = char_insert(dot, c);
     3486            dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
    30723487        }
    30733488        goto dc1;
     
    31153530        //case '_': // _-
    31163531        //case '`': // `-
    3117         //case 'u': // u- FIXME- there is no undo
    31183532        //case 'v': // v-
    31193533    default:            // unrecognized command
     
    31823596    case 27:            // esc
    31833597        if (cmd_mode == 0)
    3184             indicate_error(c);
     3598            indicate_error();
    31853599        cmd_mode = 0;   // stop insrting
     3600        undo_queue_commit();
    31863601        end_cmd_q();
    31873602        last_status_cksum = 0;  // force status update
     
    32003615            YDreg = c1;
    32013616        } else {
    3202             indicate_error(c);
     3617            indicate_error();
    32033618        }
    32043619        break;
     
    32183633            dot_skip_over_ws();
    32193634        } else {
    3220             indicate_error(c);
     3635            indicate_error();
    32213636        }
    32223637        break;
     
    32313646            mark[c1] = dot;
    32323647        } else {
    3233             indicate_error(c);
     3648            indicate_error();
    32343649        }
    32353650        break;
     
    32583673                dot_right();    // move to right, can move to NL
    32593674        }
    3260         string_insert(dot, p);  // insert the string
     3675        string_insert(dot, p, ALLOW_UNDO);  // insert the string
    32613676        end_cmd_q();    // stop adding to q
    32623677        break;
     
    32653680            p = begin_line(dot);
    32663681            q = end_line(dot);
    3267             p = text_hole_delete(p, q); // delete cur line
    3268             p += string_insert(p, reg[Ureg]);   // insert orig line
     3682            p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
     3683            p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
    32693684            dot = p;
    32703685            dot_skip_over_ws();
     
    32723687        break;
    32733688#endif /* FEATURE_VI_YANKMARK */
     3689#if ENABLE_FEATURE_VI_UNDO
     3690    case 'u':   // u- undo last operation
     3691        undo_pop();
     3692        break;
     3693#endif
    32743694    case '$':           // $- goto end of line
    32753695    case KEYCODE_END:       // Cursor Key End
     
    32873707                p = find_pair(q, *q);
    32883708                if (p == NULL) {
    3289                     indicate_error(c);
     3709                    indicate_error();
    32903710                } else {
    32913711                    dot = p;
     
    32953715        }
    32963716        if (*q == '\n')
    3297             indicate_error(c);
     3717            indicate_error();
    32983718        break;
    32993719    case 'f':           // f- forward to a user specified char
     
    34243844        break;
    34253845#endif /* FEATURE_VI_SEARCH */
    3426     case '0':           // 0- goto begining of line
     3846    case '0':           // 0- goto beginning of line
    34273847    case '1':           // 1-
    34283848    case '2':           // 2-
     
    34423862    case ':':           // :- the colon mode commands
    34433863        p = get_input_line(":");    // get input line- use "status line"
    3444 #if ENABLE_FEATURE_VI_COLON
    34453864        colon(p);       // execute the command
    3446 #else
    3447         if (*p == ':')
    3448             p++;                // move past the ':'
    3449         cnt = strlen(p);
    3450         if (cnt <= 0)
    3451             break;
    3452         if (strncmp(p, "quit", cnt) == 0
    3453          || strncmp(p, "q!", cnt) == 0   // delete lines
    3454         ) {
    3455             if (file_modified && p[1] != '!') {
    3456                 status_line_bold("No write since last change (:%s! overrides)", p);
    3457             } else {
    3458                 editing = 0;
    3459             }
    3460         } else if (strncmp(p, "write", cnt) == 0
    3461                 || strncmp(p, "wq", cnt) == 0
    3462                 || strncmp(p, "wn", cnt) == 0
    3463                 || (p[0] == 'x' && !p[1])
    3464         ) {
    3465             cnt = file_write(current_filename, text, end - 1);
    3466             if (cnt < 0) {
    3467                 if (cnt == -1)
    3468                     status_line_bold("Write error: %s", strerror(errno));
    3469             } else {
    3470                 file_modified = 0;
    3471                 last_file_modified = -1;
    3472                 status_line("\"%s\" %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
    3473                 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
    3474                  || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
    3475                 ) {
    3476                     editing = 0;
    3477                 }
    3478             }
    3479         } else if (strncmp(p, "file", cnt) == 0) {
    3480             last_status_cksum = 0;  // force status update
    3481         } else if (sscanf(p, "%d", &j) > 0) {
    3482             dot = find_line(j);     // go to line # j
    3483             dot_skip_over_ws();
    3484         } else {        // unrecognized cmd
    3485             not_implemented(p);
    3486         }
    3487 #endif /* !FEATURE_VI_COLON */
    34883865        break;
    34893866    case '<':           // <- Left  shift something
     
    34923869        c1 = get_one_char();    // get the type of thing to delete
    34933870        find_range(&p, &q, c1);
    3494         yank_delete(p, q, 1, YANKONLY); // save copy before change
     3871        yank_delete(p, q, 1, YANKONLY, NO_UNDO);    // save copy before change
    34953872        p = begin_line(p);
    34963873        q = end_line(q);
     
    35013878                if (*p == '\t') {
    35023879                    // shrink buffer 1 char
    3503                     text_hole_delete(p, p);
     3880                    text_hole_delete(p, p, NO_UNDO);
    35043881                } else if (*p == ' ') {
    35053882                    // we should be calculating columns, not just SPACE
    35063883                    for (j = 0; *p == ' ' && j < tabstop; j++) {
    3507                         text_hole_delete(p, p);
     3884                        text_hole_delete(p, p, NO_UNDO);
    35083885                    }
    35093886                }
    35103887            } else if (c == '>') {
    35113888                // shift right -- add tab or 8 spaces
    3512                 char_insert(p, '\t');
     3889                char_insert(p, '\t', ALLOW_UNDO);
    35133890            }
    35143891        }
     
    35453922        dot = dollar_line(dot); // move to before NL
    35463923        // copy text into a register and delete
    3547         dot = yank_delete(save_dot, dot, 0, YANKDEL);   // delete to e-o-l
     3924        dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO);   // delete to e-o-l
    35483925        if (c == 'C')
    35493926            goto dc_i;  // start inserting
     
    35903967 dc_i:
    35913968        cmd_mode = 1;   // start inserting
     3969        undo_queue_commit();    // commit queue when cmd_mode changes
    35923970        break;
    35933971    case 'J':           // J- join current and next lines together
     
    35953973            dot_end();      // move to NL
    35963974            if (dot < end - 1) {    // make sure not last char in text[]
     3975#if ENABLE_FEATURE_VI_UNDO
     3976                undo_push(dot, 1, UNDO_DEL);
    35973977                *dot++ = ' ';   // replace NL with space
    3598                 file_modified++;
     3978                undo_push((dot - 1), 1, UNDO_INS_CHAIN);
     3979#else
     3980                *dot++ = ' ';
     3981                modified_count++;
     3982#endif
    35993983                while (isblank(*dot)) { // delete leading WS
    3600                     dot_delete();
     3984                    text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
    36013985                }
    36023986            }
     
    36274011    case 'o':           // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
    36284012            dot_end();
    3629             dot = char_insert(dot, '\n');
     4013            dot = char_insert(dot, '\n', ALLOW_UNDO);
    36304014        } else {
    36314015            dot_begin();    // 0
    3632             dot = char_insert(dot, '\n');   // i\n ESC
     4016            dot = char_insert(dot, '\n', ALLOW_UNDO);   // i\n ESC
    36334017            dot_prev(); // -
    36344018        }
     
    36384022 dc5:
    36394023        cmd_mode = 2;
     4024        undo_queue_commit();
    36404025        break;
    36414026    case KEYCODE_DELETE:
    3642         c = 'x';
    3643         // fall through
     4027        if (dot < end - 1)
     4028            dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
     4029        break;
    36444030    case 'X':           // X- delete char before dot
    36454031    case 'x':           // x- delete the current char
     
    36524038                if (c == 'X')
    36534039                    dot--;  // delete prev char
    3654                 dot = yank_delete(dot, dot, 0, YANKDEL);    // delete char
     4040                dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO);    // delete char
    36554041            }
    36564042        } while (--cmdcnt > 0);
     
    36634049        c1 = get_one_char();
    36644050        if (c1 != 'Z') {
    3665             indicate_error(c);
     4051            indicate_error();
    36664052            break;
    36674053        }
    3668         if (file_modified) {
     4054        if (modified_count) {
    36694055            if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
    3670                 status_line_bold("\"%s\" File is read only", current_filename);
     4056                status_line_bold("'%s' is read only", current_filename);
    36714057                break;
    36724058            }
     
    37234109        // determine range, and whether it spans lines
    37244110        ml = find_range(&p, &q, c1);
     4111        place_cursor(0, 0);
    37254112        if (c1 == 27) { // ESC- user changed mind and wants out
    37264113            c = c1 = 27;    // Escape- do nothing
     
    37344121                }
    37354122            }
    3736             dot = yank_delete(p, q, ml, yf);    // delete word
     4123            dot = yank_delete(p, q, ml, yf, ALLOW_UNDO);    // delete word
    37374124        } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
    37384125            // partial line copy text into a register and delete
    3739             dot = yank_delete(p, q, ml, yf);    // delete word
     4126            dot = yank_delete(p, q, ml, yf, ALLOW_UNDO);    // delete word
    37404127        } else if (strchr("cdykjHL+-{}\r\n", c1)) {
    37414128            // whole line copy text into a register and delete
    3742             dot = yank_delete(p, q, ml, yf);    // delete lines
     4129            dot = yank_delete(p, q, ml, yf, ALLOW_UNDO);    // delete lines
    37434130            whole = 1;
    37444131        } else {
     
    37464133            c = c1 = 27;    // error-
    37474134            ml = 0;
    3748             indicate_error(c);
     4135            indicate_error();
    37494136        }
    37504137        if (ml && whole) {
    37514138            if (c == 'c') {
    3752                 dot = char_insert(dot, '\n');
     4139                dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
    37534140                // on the last line of file don't move to prev line
    37544141                if (whole && dot != (end-1)) {
     
    37964183        c1 = get_one_char();    // get the replacement char
    37974184        if (*dot != '\n') {
     4185#if ENABLE_FEATURE_VI_UNDO
     4186            undo_push(dot, 1, UNDO_DEL);
    37984187            *dot = c1;
    3799             file_modified++;
     4188            undo_push(dot, 1, UNDO_INS_CHAIN);
     4189#else
     4190            *dot = c1;
     4191            modified_count++;
     4192#endif
    38004193        }
    38014194        end_cmd_q();    // stop adding to q
     
    38374230    case '~':           // ~- flip the case of letters   a-z -> A-Z
    38384231        do {
     4232#if ENABLE_FEATURE_VI_UNDO
     4233            if (islower(*dot)) {
     4234                undo_push(dot, 1, UNDO_DEL);
     4235                *dot = toupper(*dot);
     4236                undo_push(dot, 1, UNDO_INS_CHAIN);
     4237            } else if (isupper(*dot)) {
     4238                undo_push(dot, 1, UNDO_DEL);
     4239                *dot = tolower(*dot);
     4240                undo_push(dot, 1, UNDO_INS_CHAIN);
     4241            }
     4242#else
    38394243            if (islower(*dot)) {
    38404244                *dot = toupper(*dot);
    3841                 file_modified++;
     4245                modified_count++;
    38424246            } else if (isupper(*dot)) {
    38434247                *dot = tolower(*dot);
    3844                 file_modified++;
    3845             }
     4248                modified_count++;
     4249            }
     4250#endif
    38464251            dot_right();
    38474252        } while (--cmdcnt > 0);
     
    38734278    // if text[] just became empty, add back an empty line
    38744279    if (end == text) {
    3875         char_insert(text, '\n');    // start empty buf with dummy line
     4280        char_insert(text, '\n', NO_UNDO);   // start empty buf with dummy line
    38764281        dot = text;
    38774282    }
Note: See TracChangeset for help on using the changeset viewer.