/*	$OpenBSD: word.c,v 1.23 2026/02/23 09:36:50 op Exp $	*/

/* This file is in the public domain. */

/*
 *		Word mode commands.
 * The routines in this file implement commands that work word at a time.
 * There are all sorts of word mode commands.
 */

#include <sys/queue.h>
#include <signal.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "def.h"

RSIZE	countfword(void);
int	grabword(char **);

/*
 * Move the cursor backward by "n" words. All of the details of motion are
 * performed by the "backchar" and "forwchar" routines.
 */
int
backword(int f, int n)
{
	if (n < 0)
		return (forwword(f | FFRAND, -n));
	if (backchar(FFRAND, 1) == FALSE)
		return (FALSE);
	while (n--) {
		while (inword() == FALSE) {
			if (backchar(FFRAND, 1) == FALSE)
				return (TRUE);
		}
		while (inword() != FALSE) {
			if (backchar(FFRAND, 1) == FALSE)
				return (TRUE);
		}
	}
	return (forwchar(FFRAND, 1));
}

/*
 * Move the cursor forward by the specified number of words.  All of the
 * motion is done by "forwchar".
 */
int
forwword(int f, int n)
{
	if (n < 0)
		return (backword(f | FFRAND, -n));
	while (n--) {
		while (inword() == FALSE) {
			if (forwchar(FFRAND, 1) == FALSE)
				return (TRUE);
		}
		while (inword() != FALSE) {
			if (forwchar(FFRAND, 1) == FALSE)
				return (TRUE);
		}
	}
	return (TRUE);
}

/*
 * Transpose 2 words. 
 * The function below is artificially restricted to only a maximum of 1 iteration
 * at the moment because the 'undo' functionality within mg needs amended for
 * multiple movements of point, backwards and forwards.
 */
int
transposeword(int f, int n)
{
	struct line	*tmp1_w_dotp = NULL;
	struct line	*tmp2_w_dotp = NULL;
	int		 tmp2_w_doto = 0;
	int		 tmp1_w_dotline = 0;
	int		 tmp2_w_dotline = 0;
	int		 tmp1_w_doto;
	int		 i;		/* start-of-line space counter */
	int		 ret, s;
	int		 newline;
	int		 leave = 0;
	int		 tmp_len;
	char		*word1 = NULL;
	char		*word2 = NULL;
	char		*chr;

	if (n == 0)
		return (TRUE);

	n = 1; /* remove this line to allow muliple-iterations */

	if ((s = checkdirty(curbp)) != TRUE)
		return (s);
	if (curbp->b_flag & BFREADONLY) {
		dobeep();
		ewprintf("Buffer is read-only");
		return (FALSE);
	}
	undo_boundary_enable(FFRAND, 0);

	/* go backwards to find the start of a word to transpose. */
	(void)backword(FFRAND, 1);
	ret = grabword(&word1);
	if (ret == ABORT) {
		ewprintf("No word to the left to tranpose.");
		return (FALSE);
	}
	if (ret < 0) {
		dobeep();
		ewprintf("Error copying word: %s", strerror(ret));
		free(word1);
		return (FALSE);
	}

	while (n-- > 0) {
		i = 0;
		newline = 0;

		tmp1_w_doto = curwp->w_doto;
		tmp1_w_dotline = curwp->w_dotline;
		tmp1_w_dotp = curwp->w_dotp;

		/* go forward and find next word. */
		while (inword() == FALSE) {
			if (forwchar(FFRAND, 1) == FALSE) {
				leave = 1;
				if (tmp1_w_dotline < curwp->w_dotline)
					curwp->w_dotline--;
				ewprintf("Don't have two things to transpose");
				break;
			}
			if (curwp->w_doto == 0) {
				newline = 1;
				i = 0;
			} else if (newline)
				i++;
		}
		if (leave) {
			tmp2_w_doto = tmp1_w_doto;
			tmp2_w_dotline = tmp1_w_dotline;
			tmp2_w_dotp = tmp1_w_dotp;
			break;
		}
		tmp2_w_doto = curwp->w_doto;
		tmp2_w_dotline = curwp->w_dotline;
		tmp2_w_dotp = curwp->w_dotp;

		ret = grabword(&word2);
		if (ret < 0 || ret == ABORT) {
			dobeep();
			ewprintf("Error copying word: %s", strerror(ret));
			free(word1);
			return (FALSE);
		}
		tmp_len = strlen(word2);
		tmp2_w_doto += tmp_len;

		curwp->w_doto = tmp1_w_doto;
		curwp->w_dotline = tmp1_w_dotline;
		curwp->w_dotp = tmp1_w_dotp;

		/* insert shuffled along word */
		for (chr = word2; *chr != '\0'; ++chr)
			linsert(1, *chr);

		if (newline)
			tmp2_w_doto = i;

		curwp->w_doto = tmp2_w_doto;
		curwp->w_dotline = tmp2_w_dotline;
		curwp->w_dotp = tmp2_w_dotp;

		free(word2);
		word2 = NULL;
	}
	curwp->w_doto = tmp2_w_doto;
	curwp->w_dotline = tmp2_w_dotline;
	curwp->w_dotp = tmp2_w_dotp;

	/* insert very first word in its new position */
	for (chr = word1; *chr != '\0'; ++chr)
		linsert(1, *chr);

	if (leave)
		(void)backword(FFRAND, 1);

	free(word1);
	free(word2);

	undo_boundary_enable(FFRAND, 1);

	return (TRUE);
}

/*
 * copy and delete word.
*/
int
grabword(char **word)
{
	size_t len = 0, cap = 0;
	char *t;

	while (inword() == TRUE) {
		if (cap == 0 || len == cap - 1) {
			t = recallocarray(*word, cap, cap + 8, 1);
			if (t == NULL) {
				free(*word);
				*word = NULL;
				return (errno);
			}
			cap += 8;
			*word = t;
		}

		(*word)[len++] = lgetc(curwp->w_dotp, curwp->w_doto);
		(void)forwdel(FFRAND, 1);
	}
	if (*word == NULL)
		return (ABORT);
	return (TRUE);
}

/*
 * Move the cursor forward by the specified number of words.  As you move,
 * convert any characters to upper case.
 */
int
upperword(int f, int n)
{
	int	c, s;
	RSIZE	size;

	if ((s = checkdirty(curbp)) != TRUE)
		return (s);
	if (curbp->b_flag & BFREADONLY) {
		dobeep();
		ewprintf("Buffer is read-only");
		return (FALSE);
	}

	if (n < 0)
		return (FALSE);
	while (n--) {
		while (inword() == FALSE) {
			if (forwchar(FFRAND, 1) == FALSE)
				return (TRUE);
		}
		size = countfword();
		undo_add_change(curwp->w_dotp, curwp->w_doto, size);

		while (inword() != FALSE) {
			c = lgetc(curwp->w_dotp, curwp->w_doto);
			if (ISLOWER(c) != FALSE) {
				c = TOUPPER(c);
				lputc(curwp->w_dotp, curwp->w_doto, c);
				lchange(WFFULL);
			}
			if (forwchar(FFRAND, 1) == FALSE)
				return (TRUE);
		}
	}
	return (TRUE);
}

/*
 * Move the cursor forward by the specified number of words.  As you move
 * convert characters to lower case.
 */
int
lowerword(int f, int n)
{
	int	c, s;
	RSIZE	size;

	if ((s = checkdirty(curbp)) != TRUE)
		return (s);
	if (curbp->b_flag & BFREADONLY) {
		dobeep();
		ewprintf("Buffer is read-only");
		return (FALSE);
	}
	if (n < 0)
		return (FALSE);
	while (n--) {
		while (inword() == FALSE) {
			if (forwchar(FFRAND, 1) == FALSE)
				return (TRUE);
		}
		size = countfword();
		undo_add_change(curwp->w_dotp, curwp->w_doto, size);

		while (inword() != FALSE) {
			c = lgetc(curwp->w_dotp, curwp->w_doto);
			if (ISUPPER(c) != FALSE) {
				c = TOLOWER(c);
				lputc(curwp->w_dotp, curwp->w_doto, c);
				lchange(WFFULL);
			}
			if (forwchar(FFRAND, 1) == FALSE)
				return (TRUE);
		}
	}
	return (TRUE);
}

/*
 * Move the cursor forward by the specified number of words.  As you move
 * convert the first character of the word to upper case, and subsequent
 * characters to lower case.  Error if you try to move past the end of the
 * buffer.
 */
int
capword(int f, int n)
{
	int	c, s;
	RSIZE	size;

	if ((s = checkdirty(curbp)) != TRUE)
		return (s);
	if (curbp->b_flag & BFREADONLY) {
		dobeep();
		ewprintf("Buffer is read-only");
		return (FALSE);
	}

	if (n < 0)
		return (FALSE);
	while (n--) {
		while (inword() == FALSE) {
			if (forwchar(FFRAND, 1) == FALSE)
				return (TRUE);
		}
		size = countfword();
		undo_add_change(curwp->w_dotp, curwp->w_doto, size);

		if (inword() != FALSE) {
			c = lgetc(curwp->w_dotp, curwp->w_doto);
			if (ISLOWER(c) != FALSE) {
				c = TOUPPER(c);
				lputc(curwp->w_dotp, curwp->w_doto, c);
				lchange(WFFULL);
			}
			if (forwchar(FFRAND, 1) == FALSE)
				return (TRUE);
			while (inword() != FALSE) {
				c = lgetc(curwp->w_dotp, curwp->w_doto);
				if (ISUPPER(c) != FALSE) {
					c = TOLOWER(c);
					lputc(curwp->w_dotp, curwp->w_doto, c);
					lchange(WFFULL);
				}
				if (forwchar(FFRAND, 1) == FALSE)
					return (TRUE);
			}
		}
	}
	return (TRUE);
}

/*
 * Count characters in word, from current position
 */
RSIZE
countfword()
{
	RSIZE		 size;
	struct line	*dotp;
	int		 doto;

	dotp = curwp->w_dotp;
	doto = curwp->w_doto;
	size = 0;

	while (inword() != FALSE) {
		if (forwchar(FFRAND, 1) == FALSE)
			/* hit the end of the buffer */
			goto out;
		++size;
	}
out:
	curwp->w_dotp = dotp;
	curwp->w_doto = doto;
	return (size);
}


/*
 * Kill forward by "n" words.
 */
int
delfword(int f, int n)
{
	RSIZE		 size;
	struct line	*dotp;
	int		 doto;
	int s;

	if ((s = checkdirty(curbp)) != TRUE)
		return (s);
	if (curbp->b_flag & BFREADONLY) {
		dobeep();
		ewprintf("Buffer is read-only");
		return (FALSE);
	}
	if (n < 0)
		return (FALSE);

	/* purge kill buffer */
	if ((lastflag & CFKILL) == 0)
		kdelete();

	thisflag |= CFKILL;
	dotp = curwp->w_dotp;
	doto = curwp->w_doto;
	size = 0;

	while (n--) {
		while (inword() == FALSE) {
			if (forwchar(FFRAND, 1) == FALSE)
				/* hit the end of the buffer */
				goto out;
			++size;
		}
		while (inword() != FALSE) {
			if (forwchar(FFRAND, 1) == FALSE)
				/* hit the end of the buffer */
				goto out;
			++size;
		}
	}
out:
	curwp->w_dotp = dotp;
	curwp->w_doto = doto;
	return (ldelete(size, KFORW));
}

/*
 * Kill backwards by "n" words.  The rules for success and failure are now
 * different, to prevent strange behavior at the start of the buffer.  The
 * command only fails if something goes wrong with the actual delete of the
 * characters.  It is successful even if no characters are deleted, or if you
 * say delete 5 words, and there are only 4 words left.  I considered making
 * the first call to "backchar" special, but decided that that would just be
 * weird. Normally this is bound to "M-Rubout" and to "M-Backspace".
 */
int
delbword(int f, int n)
{
	RSIZE	size;
	int s;

	if ((s = checkdirty(curbp)) != TRUE)
		return (s);
	if (curbp->b_flag & BFREADONLY) {
		dobeep();
		ewprintf("Buffer is read-only");
		return (FALSE);
	}

	if (n < 0)
		return (FALSE);

	/* purge kill buffer */
	if ((lastflag & CFKILL) == 0)
		kdelete();
	thisflag |= CFKILL;
	if (backchar(FFRAND, 1) == FALSE)
		/* hit buffer start */
		return (TRUE);

	/* one deleted */
	size = 1;
	while (n--) {
		while (inword() == FALSE) {
			if (backchar(FFRAND, 1) == FALSE)
				/* hit buffer start */
				goto out;
			++size;
		}
		while (inword() != FALSE) {
			if (backchar(FFRAND, 1) == FALSE)
				/* hit buffer start */
				goto out;
			++size;
		}
	}
	if (forwchar(FFRAND, 1) == FALSE)
		return (FALSE);

	/* undo assumed delete */
	--size;
out:
	return (ldelete(size, KBACK));
}

/*
 * Return TRUE if the character at dot is a character that is considered to be
 * part of a word. The word character list is hard coded. Should be settable.
 */
int
inword(void)
{
	/* can't use lgetc in ISWORD due to bug in OSK cpp */
	return (curwp->w_doto != llength(curwp->w_dotp) &&
	    ISWORD(curwp->w_dotp->l_text[curwp->w_doto]));
}
