root/lib/libedit/emacs.c
/*      $NetBSD: emacs.c,v 1.39 2025/12/14 18:07:40 christos Exp $      */

/*-
 * Copyright (c) 1992, 1993
 *      The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Christos Zoulas of Cornell University.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "config.h"
#if !defined(lint) && !defined(SCCSID)
#if 0
static char sccsid[] = "@(#)emacs.c     8.1 (Berkeley) 6/4/93";
#else
__RCSID("$NetBSD: emacs.c,v 1.39 2025/12/14 18:07:40 christos Exp $");
#endif
#endif /* not lint && not SCCSID */

/*
 * emacs.c: Emacs functions
 */
#include <ctype.h>

#include "el.h"
#include "emacs.h"
#include "fcns.h"

/* em_delete_or_list():
 *      Delete character under cursor or list completions if at end of line
 *      [^D]
 */
libedit_private el_action_t
/*ARGSUSED*/
em_delete_or_list(EditLine *el, wint_t c)
{

        if (el->el_line.cursor == el->el_line.lastchar) {
                                        /* if I'm at the end */
                if (el->el_line.cursor == el->el_line.buffer) {
                                        /* and the beginning */
                        terminal_writec(el, c); /* then do an EOF */
                        return CC_EOF;
                } else {
                        /*
                         * Here we could list completions, but it is an
                         * error right now
                         */
                        terminal_beep(el);
                        return CC_ERROR;
                }
        } else {
                if (el->el_state.doingarg)
                        c_delafter(el, el->el_state.argument);
                else
                        c_delafter1(el);
                if (el->el_line.cursor > el->el_line.lastchar)
                        el->el_line.cursor = el->el_line.lastchar;
                                /* bounds check */
                return CC_REFRESH;
        }
}


/* em_delete_next_word():
 *      Cut from cursor to end of current word
 *      [M-d]
 */
libedit_private el_action_t
/*ARGSUSED*/
em_delete_next_word(EditLine *el, wint_t c __attribute__((__unused__)))
{
        wchar_t *cp, *p, *kp;

        if (el->el_line.cursor == el->el_line.lastchar)
                return CC_ERROR;

        cp = c__next_word(el, el->el_line.cursor, el->el_line.lastchar,
            el->el_state.argument, ce__isword);

        for (p = el->el_line.cursor, kp = el->el_chared.c_kill.buf; p < cp; p++)
                                /* save the text */
                *kp++ = *p;
        el->el_chared.c_kill.last = kp;

        c_delafter(el, (int)(cp - el->el_line.cursor)); /* delete after dot */
        if (el->el_line.cursor > el->el_line.lastchar)
                el->el_line.cursor = el->el_line.lastchar;
                                /* bounds check */
        return CC_REFRESH;
}


/* em_yank():
 *      Paste cut buffer at cursor position
 *      [^Y]
 */
libedit_private el_action_t
/*ARGSUSED*/
em_yank(EditLine *el, wint_t c __attribute__((__unused__)))
{
        wchar_t *kp, *cp;

        if (el->el_chared.c_kill.last == el->el_chared.c_kill.buf)
                return CC_NORM;

        if (el->el_line.lastchar +
            (el->el_chared.c_kill.last - el->el_chared.c_kill.buf) >=
            el->el_line.limit)
                return CC_ERROR;

        el->el_chared.c_kill.mark = el->el_line.cursor;

        /* open the space, */
        c_insert(el,
            (int)(el->el_chared.c_kill.last - el->el_chared.c_kill.buf));
        cp = el->el_line.cursor;
        /* copy the chars */
        for (kp = el->el_chared.c_kill.buf; kp < el->el_chared.c_kill.last; kp++)
                *cp++ = *kp;

        /* if an arg, cursor at beginning else cursor at end */
        if (el->el_state.argument == 1)
                el->el_line.cursor = cp;

        return CC_REFRESH;
}


/* em_kill_line():
 *      Cut the entire line and save in cut buffer
 *      [^U]
 */
libedit_private el_action_t
/*ARGSUSED*/
em_kill_line(EditLine *el, wint_t c __attribute__((__unused__)))
{
        wchar_t *kp, *cp;

        cp = el->el_line.buffer;
        kp = el->el_chared.c_kill.buf;
        while (cp < el->el_line.lastchar)
                *kp++ = *cp++;  /* copy it */
        el->el_chared.c_kill.last = kp;
                                /* zap! -- delete all of it */
        el->el_line.lastchar = el->el_line.buffer;
        el->el_line.cursor = el->el_line.buffer;
        return CC_REFRESH;
}


/* em_kill_region():
 *      Cut area between mark and cursor and save in cut buffer
 *      [^W]
 */
libedit_private el_action_t
/*ARGSUSED*/
em_kill_region(EditLine *el, wint_t c __attribute__((__unused__)))
{
        wchar_t *kp, *cp;

        if (!el->el_chared.c_kill.mark)
                return CC_ERROR;

        if (el->el_chared.c_kill.mark > el->el_line.cursor) {
                cp = el->el_line.cursor;
                kp = el->el_chared.c_kill.buf;
                while (cp < el->el_chared.c_kill.mark)
                        *kp++ = *cp++;  /* copy it */
                el->el_chared.c_kill.last = kp;
                c_delafter(el, (int)(cp - el->el_line.cursor));
        } else {                /* mark is before cursor */
                cp = el->el_chared.c_kill.mark;
                kp = el->el_chared.c_kill.buf;
                while (cp < el->el_line.cursor)
                        *kp++ = *cp++;  /* copy it */
                el->el_chared.c_kill.last = kp;
                c_delbefore(el, (int)(cp - el->el_chared.c_kill.mark));
                el->el_line.cursor = el->el_chared.c_kill.mark;
        }
        return CC_REFRESH;
}


/* em_copy_region():
 *      Copy area between mark and cursor to cut buffer
 *      [M-W]
 */
libedit_private el_action_t
/*ARGSUSED*/
em_copy_region(EditLine *el, wint_t c __attribute__((__unused__)))
{
        wchar_t *kp, *cp;

        if (!el->el_chared.c_kill.mark)
                return CC_ERROR;

        if (el->el_chared.c_kill.mark > el->el_line.cursor) {
                cp = el->el_line.cursor;
                kp = el->el_chared.c_kill.buf;
                while (cp < el->el_chared.c_kill.mark)
                        *kp++ = *cp++;  /* copy it */
                el->el_chared.c_kill.last = kp;
        } else {
                cp = el->el_chared.c_kill.mark;
                kp = el->el_chared.c_kill.buf;
                while (cp < el->el_line.cursor)
                        *kp++ = *cp++;  /* copy it */
                el->el_chared.c_kill.last = kp;
        }
        return CC_NORM;
}


/* em_gosmacs_transpose():
 *      Exchange the two characters before the cursor
 *      Gosling emacs transpose chars [^T]
 */
libedit_private el_action_t
em_gosmacs_transpose(EditLine *el, wint_t c)
{

        if (el->el_line.cursor > &el->el_line.buffer[1]) {
                /* must have at least two chars entered */
                c = el->el_line.cursor[-2];
                el->el_line.cursor[-2] = el->el_line.cursor[-1];
                el->el_line.cursor[-1] = c;
                return CC_REFRESH;
        } else
                return CC_ERROR;
}


/* em_next_word():
 *      Move next to end of current word
 *      [M-f]
 */
libedit_private el_action_t
/*ARGSUSED*/
em_next_word(EditLine *el, wint_t c __attribute__((__unused__)))
{
        if (el->el_line.cursor == el->el_line.lastchar)
                return CC_ERROR;

        el->el_line.cursor = c__next_word(el, el->el_line.cursor,
            el->el_line.lastchar,
            el->el_state.argument,
            ce__isword);

        if (el->el_map.type == MAP_VI)
                if (el->el_chared.c_vcmd.action != NOP) {
                        cv_delfini(el);
                        return CC_REFRESH;
                }
        return CC_CURSOR;
}


/* em_upper_case():
 *      Uppercase the characters from cursor to end of current word
 *      [M-u]
 */
libedit_private el_action_t
/*ARGSUSED*/
em_upper_case(EditLine *el, wint_t c __attribute__((__unused__)))
{
        wchar_t *cp, *ep;

        ep = c__next_word(el, el->el_line.cursor, el->el_line.lastchar,
            el->el_state.argument, ce__isword);

        for (cp = el->el_line.cursor; cp < ep; cp++)
                if (iswlower(*cp))
                        *cp = towupper(*cp);

        el->el_line.cursor = ep;
        if (el->el_line.cursor > el->el_line.lastchar)
                el->el_line.cursor = el->el_line.lastchar;
        return CC_REFRESH;
}


/* em_capitol_case():
 *      Capitalize the characters from cursor to end of current word
 *      [M-c]
 */
libedit_private el_action_t
/*ARGSUSED*/
em_capitol_case(EditLine *el, wint_t c __attribute__((__unused__)))
{
        wchar_t *cp, *ep;

        ep = c__next_word(el, el->el_line.cursor, el->el_line.lastchar,
            el->el_state.argument, ce__isword);

        for (cp = el->el_line.cursor; cp < ep; cp++) {
                if (iswalpha(*cp)) {
                        if (iswlower(*cp))
                                *cp = towupper(*cp);
                        cp++;
                        break;
                }
        }
        for (; cp < ep; cp++)
                if (iswupper(*cp))
                        *cp = towlower(*cp);

        el->el_line.cursor = ep;
        if (el->el_line.cursor > el->el_line.lastchar)
                el->el_line.cursor = el->el_line.lastchar;
        return CC_REFRESH;
}


/* em_lower_case():
 *      Lowercase the characters from cursor to end of current word
 *      [M-l]
 */
libedit_private el_action_t
/*ARGSUSED*/
em_lower_case(EditLine *el, wint_t c __attribute__((__unused__)))
{
        wchar_t *cp, *ep;

        ep = c__next_word(el, el->el_line.cursor, el->el_line.lastchar,
            el->el_state.argument, ce__isword);

        for (cp = el->el_line.cursor; cp < ep; cp++)
                if (iswupper(*cp))
                        *cp = towlower(*cp);

        el->el_line.cursor = ep;
        if (el->el_line.cursor > el->el_line.lastchar)
                el->el_line.cursor = el->el_line.lastchar;
        return CC_REFRESH;
}


/* em_set_mark():
 *      Set the mark at cursor
 *      [^@]
 */
libedit_private el_action_t
/*ARGSUSED*/
em_set_mark(EditLine *el, wint_t c __attribute__((__unused__)))
{

        el->el_chared.c_kill.mark = el->el_line.cursor;
        return CC_NORM;
}


/* em_exchange_mark():
 *      Exchange the cursor and mark
 *      [^X^X]
 */
libedit_private el_action_t
/*ARGSUSED*/
em_exchange_mark(EditLine *el, wint_t c __attribute__((__unused__)))
{
        wchar_t *cp;

        cp = el->el_line.cursor;
        el->el_line.cursor = el->el_chared.c_kill.mark;
        el->el_chared.c_kill.mark = cp;
        return CC_CURSOR;
}


/* em_universal_argument():
 *      Universal argument (argument times 4)
 *      [^U]
 */
libedit_private el_action_t
/*ARGSUSED*/
em_universal_argument(EditLine *el, wint_t c __attribute__((__unused__)))
{                               /* multiply current argument by 4 */

        if (el->el_state.argument > 1000000)
                return CC_ERROR;
        el->el_state.doingarg = 1;
        el->el_state.argument *= 4;
        return CC_ARGHACK;
}


/* em_meta_next():
 *      Add 8th bit to next character typed
 *      [<ESC>]
 */
libedit_private el_action_t
/*ARGSUSED*/
em_meta_next(EditLine *el, wint_t c __attribute__((__unused__)))
{

        el->el_state.metanext = 1;
        return CC_ARGHACK;
}


/* em_toggle_overwrite():
 *      Switch from insert to overwrite mode or vice versa
 */
libedit_private el_action_t
/*ARGSUSED*/
em_toggle_overwrite(EditLine *el, wint_t c __attribute__((__unused__)))
{

        el->el_state.inputmode = (el->el_state.inputmode == MODE_INSERT) ?
            MODE_REPLACE : MODE_INSERT;
        return CC_NORM;
}


/* em_copy_prev_word():
 *      Copy current word to cursor
 */
libedit_private el_action_t
/*ARGSUSED*/
em_copy_prev_word(EditLine *el, wint_t c __attribute__((__unused__)))
{
        wchar_t *cp, *oldc, *dp;

        if (el->el_line.cursor == el->el_line.buffer)
                return CC_ERROR;

        /* does a bounds check */
        cp = c__prev_word(el, el->el_line.cursor, el->el_line.buffer,
            el->el_state.argument, ce__isword);

        c_insert(el, (int)(el->el_line.cursor - cp));
        oldc = el->el_line.cursor;
        for (dp = oldc; cp < oldc && dp < el->el_line.lastchar; cp++)
                *dp++ = *cp;

        el->el_line.cursor = dp;/* put cursor at end */

        return CC_REFRESH;
}


/* em_inc_search_next():
 *      Emacs incremental next search
 */
libedit_private el_action_t
/*ARGSUSED*/
em_inc_search_next(EditLine *el, wint_t c __attribute__((__unused__)))
{

        el->el_search.patlen = 0;
        return ce_inc_search(el, ED_SEARCH_NEXT_HISTORY);
}


/* em_inc_search_prev():
 *      Emacs incremental reverse search
 */
libedit_private el_action_t
/*ARGSUSED*/
em_inc_search_prev(EditLine *el, wint_t c __attribute__((__unused__)))
{

        el->el_search.patlen = 0;
        return ce_inc_search(el, ED_SEARCH_PREV_HISTORY);
}


/* em_delete_prev_char():
 *      Delete the character to the left of the cursor
 *      [^?]
 */
libedit_private el_action_t
/*ARGSUSED*/
em_delete_prev_char(EditLine *el, wint_t c __attribute__((__unused__)))
{

        if (el->el_line.cursor <= el->el_line.buffer)
                return CC_ERROR;

        if (el->el_state.doingarg)
                c_delbefore(el, el->el_state.argument);
        else
                c_delbefore1(el);
        el->el_line.cursor -= el->el_state.argument;
        if (el->el_line.cursor < el->el_line.buffer)
                el->el_line.cursor = el->el_line.buffer;
        return CC_REFRESH;
}