[BACK]Return to vs_split.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / vi / vi

File: [local] / src / usr.bin / vi / vi / vs_split.c (download)

Revision 1.10, Tue Oct 27 23:59:49 2009 UTC (14 years, 7 months ago) by deraadt
Branch: MAIN
CVS Tags: OPENBSD_5_4_BASE, OPENBSD_5_4, OPENBSD_5_3_BASE, OPENBSD_5_3, OPENBSD_5_2_BASE, OPENBSD_5_2, OPENBSD_5_1_BASE, OPENBSD_5_1, OPENBSD_5_0_BASE, OPENBSD_5_0, OPENBSD_4_9_BASE, OPENBSD_4_9, OPENBSD_4_8_BASE, OPENBSD_4_8, OPENBSD_4_7_BASE, OPENBSD_4_7
Changes since 1.9: +1 -5 lines

rcsid[] and sccsid[] and copyright[] are essentially unmaintained (and
unmaintainable).  these days, people use source.  these id's do not provide
any benefit, and do hurt the small install media
(the 33,000 line diff is essentially mechanical)
ok with the idea millert, ok dms

/*	$OpenBSD: vs_split.c,v 1.10 2009/10/27 23:59:49 deraadt Exp $	*/

/*-
 * Copyright (c) 1993, 1994
 *	The Regents of the University of California.  All rights reserved.
 * Copyright (c) 1993, 1994, 1995, 1996
 *	Keith Bostic.  All rights reserved.
 *
 * See the LICENSE file for redistribution information.
 */

#include "config.h"

#include <sys/types.h>
#include <sys/queue.h>
#include <sys/time.h>

#include <bitstring.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "../common/common.h"
#include "vi.h"

static SCR *vs_getbg(SCR *, char *);

/*
 * vs_split --
 *	Create a new screen.
 *
 * PUBLIC: int vs_split(SCR *, SCR *, int);
 */
int
vs_split(sp, new, ccl)
	SCR *sp, *new;
	int ccl;		/* Colon-command line split. */
{
	GS *gp;
	SMAP *smp;
	size_t half;
	int issmallscreen, splitup;

	gp = sp->gp;

	/* Check to see if it's possible. */
	/* XXX: The IS_ONELINE fix will change this, too. */
	if (sp->rows < 4) {
		msgq(sp, M_ERR,
		    "222|Screen must be larger than %d lines to split", 4 - 1);
		return (1);
	}

	/* Wait for any messages in the screen. */
	vs_resolve(sp, NULL, 1);

	half = sp->rows / 2;
	if (ccl && half > 6)
		half = 6;

	/* Get a new screen map. */
	CALLOC(sp, _HMAP(new), SMAP *, SIZE_HMAP(sp), sizeof(SMAP));
	if (_HMAP(new) == NULL)
		return (1);
	_HMAP(new)->lno = sp->lno;
	_HMAP(new)->coff = 0;
	_HMAP(new)->soff = 1;

	/*
	 * Small screens: see vs_refresh.c section 6a.  Set a flag so
	 * we know to fix the screen up later.
	 */
	issmallscreen = IS_SMALL(sp);

	/* The columns in the screen don't change. */
	new->cols = sp->cols;

	/*
	 * Split the screen, and link the screens together.  If creating a
	 * screen to edit the colon command line or the cursor is in the top
	 * half of the current screen, the new screen goes under the current
	 * screen.  Else, it goes above the current screen.
	 *
	 * Recalculate current cursor position based on sp->lno, we're called
	 * with the cursor on the colon command line.  Then split the screen
	 * in half and update the shared information.
	 */
	splitup =
	    !ccl && (vs_sm_cursor(sp, &smp) ? 0 : (smp - HMAP) + 1) >= half;
	if (splitup) {				/* Old is bottom half. */
		new->rows = sp->rows - half;	/* New. */
		new->woff = sp->woff;
		sp->rows = half;		/* Old. */
		sp->woff += new->rows;
						/* Link in before old. */
		CIRCLEQ_INSERT_BEFORE(&gp->dq, sp, new, q);

		/*
		 * If the parent is the bottom half of the screen, shift
		 * the map down to match on-screen text.
		 */
		memmove(_HMAP(sp), _HMAP(sp) + new->rows,
		    (sp->t_maxrows - new->rows) * sizeof(SMAP));
	} else {				/* Old is top half. */
		new->rows = half;		/* New. */
		sp->rows -= half;		/* Old. */
		new->woff = sp->woff + sp->rows;
						/* Link in after old. */
		CIRCLEQ_INSERT_AFTER(&gp->dq, sp, new, q);
	}

	/* Adjust maximum text count. */
	sp->t_maxrows = IS_ONELINE(sp) ? 1 : sp->rows - 1;
	new->t_maxrows = IS_ONELINE(new) ? 1 : new->rows - 1;

	/*
	 * Small screens: see vs_refresh.c, section 6a.
	 *
	 * The child may have different screen options sizes than the parent,
	 * so use them.  Guarantee that text counts aren't larger than the
	 * new screen sizes.
	 */
	if (issmallscreen) {
		/* Fix the text line count for the parent. */
		if (splitup)
			sp->t_rows -= new->rows;

		/* Fix the parent screen. */
		if (sp->t_rows > sp->t_maxrows)
			sp->t_rows = sp->t_maxrows;
		if (sp->t_minrows > sp->t_maxrows)
			sp->t_minrows = sp->t_maxrows;

		/* Fix the child screen. */
		new->t_minrows = new->t_rows = O_VAL(sp, O_WINDOW);
		if (new->t_rows > new->t_maxrows)
			new->t_rows = new->t_maxrows;
		if (new->t_minrows > new->t_maxrows)
			new->t_minrows = new->t_maxrows;
	} else {
		sp->t_minrows = sp->t_rows = IS_ONELINE(sp) ? 1 : sp->rows - 1;

		/*
		 * The new screen may be a small screen, even if the parent
		 * was not.  Don't complain if O_WINDOW is too large, we're
		 * splitting the screen so the screen is much smaller than
		 * normal.
		 */
		new->t_minrows = new->t_rows = O_VAL(sp, O_WINDOW);
		if (new->t_rows > new->rows - 1)
			new->t_minrows = new->t_rows =
			    IS_ONELINE(new) ? 1 : new->rows - 1;
	}

	/* Adjust the ends of the new and old maps. */
	_TMAP(sp) = IS_ONELINE(sp) ?
	    _HMAP(sp) : _HMAP(sp) + (sp->t_rows - 1);
	_TMAP(new) = IS_ONELINE(new) ?
	    _HMAP(new) : _HMAP(new) + (new->t_rows - 1);

	/* Reset the length of the default scroll. */
	if ((sp->defscroll = sp->t_maxrows / 2) == 0)
		sp->defscroll = 1;
	if ((new->defscroll = new->t_maxrows / 2) == 0)
		new->defscroll = 1;

	/*
	 * Initialize the screen flags:
	 *
	 * If we're in vi mode in one screen, we don't have to reinitialize.
	 * This isn't just a cosmetic fix.  The path goes like this:
	 *
	 *	return into vi(), SC_SSWITCH set
	 *	call vs_refresh() with SC_STATUS set
	 *	call vs_resolve to display the status message
	 *	call vs_refresh() because the SC_SCR_VI bit isn't set
	 *
	 * Things go downhill at this point.
	 *
	 * Draw the new screen from scratch, and add a status line.
	 */
	F_SET(new,
	    SC_SCR_REFORMAT | SC_STATUS |
	    F_ISSET(sp, SC_EX | SC_VI | SC_SCR_VI | SC_SCR_EX));
	return (0);
}

/*
 * vs_discard --
 *	Discard the screen, folding the real-estate into a related screen,
 *	if one exists, and return that screen.
 *
 * PUBLIC: int vs_discard(SCR *, SCR **);
 */
int
vs_discard(sp, spp)
	SCR *sp, **spp;
{
	SCR *nsp;
	dir_t dir;

	/*
	 * Save the old screen's cursor information.
	 *
	 * XXX
	 * If called after file_end(), and the underlying file was a tmp
	 * file, it may have gone away.
	 */
	if (sp->frp != NULL) {
		sp->frp->lno = sp->lno;
		sp->frp->cno = sp->cno;
		F_SET(sp->frp, FR_CURSORSET);
	}

	/*
	 * Add into a previous screen and then into a subsequent screen, as
	 * they're the closest to the current screen.  If that doesn't work,
	 * there was no screen to join.
	 */
	if ((nsp = CIRCLEQ_PREV(sp, q)) != CIRCLEQ_END(&sp->gp->dq)) {
		nsp->rows += sp->rows;
		sp = nsp;
		dir = FORWARD;
	} else if ((nsp = CIRCLEQ_NEXT(sp, q)) != CIRCLEQ_END(&sp->gp->dq)) {
		nsp->woff = sp->woff;
		nsp->rows += sp->rows;
		sp = nsp;
		dir = BACKWARD;
	} else {
		sp = NULL;
		dir = 0;	/* unused */
	}

	if (spp != NULL)
		*spp = sp;
	if (sp == NULL)
		return (0);
		
	/*
	 * Make no effort to clean up the discarded screen's information.  If
	 * it's not exiting, we'll do the work when the user redisplays it.
	 *
	 * Small screens: see vs_refresh.c section 6a.  Adjust text line info,
	 * unless it's a small screen.
	 *
	 * Reset the length of the default scroll.
	 */
	if (!IS_SMALL(sp))
		sp->t_rows = sp->t_minrows = sp->rows - 1;
	sp->t_maxrows = sp->rows - 1;
	sp->defscroll = sp->t_maxrows / 2;
	*(HMAP + (sp->t_rows - 1)) = *TMAP;
	TMAP = HMAP + (sp->t_rows - 1);

	/*
	 * Draw the new screen from scratch, and add a status line.
	 *
	 * XXX
	 * We could play games with the map, if this were ever to be a
	 * performance problem, but I wrote the code a few times and it
	 * was never clean or easy.
	 */
	switch (dir) {
	case FORWARD:
		vs_sm_fill(sp, OOBLNO, P_TOP);
		break;
	case BACKWARD:
		vs_sm_fill(sp, OOBLNO, P_BOTTOM);
		break;
	default:
		abort();
	}

	F_SET(sp, SC_STATUS);
	return (0);
}

/*
 * vs_fg --
 *	Background the current screen, and foreground a new one.
 *
 * PUBLIC: int vs_fg(SCR *, SCR **, CHAR_T *, int);
 */
int
vs_fg(sp, nspp, name, newscreen)
	SCR *sp, **nspp;
	CHAR_T *name;
	int newscreen;
{
	GS *gp;
	SCR *nsp;

	gp = sp->gp;

	if (newscreen)
		/* Get the specified background screen. */
		nsp = vs_getbg(sp, name);
	else
		/* Swap screens. */
		if (vs_swap(sp, &nsp, name))
			return (1);

	if ((*nspp = nsp) == NULL) {
		msgq_str(sp, M_ERR, name,
		    name == NULL ?
		    "223|There are no background screens" :
		    "224|There's no background screen editing a file named %s");
		return (1);
	}

	if (newscreen) {
		/* Remove the new screen from the background queue. */
		CIRCLEQ_REMOVE(&gp->hq, nsp, q);

		/* Split the screen; if we fail, hook the screen back in. */
		if (vs_split(sp, nsp, 0)) {
			CIRCLEQ_INSERT_TAIL(&gp->hq, nsp, q);
			return (1);
		}
	} else {
		/* Move the old screen to the background queue. */
		CIRCLEQ_REMOVE(&gp->dq, sp, q);
		CIRCLEQ_INSERT_TAIL(&gp->hq, sp, q);
	}
	return (0);
}

/*
 * vs_bg --
 *	Background the screen, and switch to the next one.
 *
 * PUBLIC: int vs_bg(SCR *);
 */
int
vs_bg(sp)
	SCR *sp;
{
	GS *gp;
	SCR *nsp;

	gp = sp->gp;

	/* Try and join with another screen. */
	if (vs_discard(sp, &nsp))
		return (1);
	if (nsp == NULL) {
		msgq(sp, M_ERR,
		    "225|You may not background your only displayed screen");
		return (1);
	}

	/* Move the old screen to the background queue. */
	CIRCLEQ_REMOVE(&gp->dq, sp, q);
	CIRCLEQ_INSERT_TAIL(&gp->hq, sp, q);

	/* Toss the screen map. */
	free(_HMAP(sp));
	_HMAP(sp) = NULL;

	/* Switch screens. */
	sp->nextdisp = nsp;
	F_SET(sp, SC_SSWITCH);

	return (0);
}

/*
 * vs_swap --
 *	Swap the current screen with a backgrounded one.
 *
 * PUBLIC: int vs_swap(SCR *, SCR **, char *);
 */
int
vs_swap(sp, nspp, name)
	SCR *sp, **nspp;
	char *name;
{
	GS *gp;
	SCR *nsp;

	gp = sp->gp;

	/* Get the specified background screen. */
	if ((*nspp = nsp = vs_getbg(sp, name)) == NULL)
		return (0);

	/*
	 * Save the old screen's cursor information.
	 *
	 * XXX
	 * If called after file_end(), and the underlying file was a tmp
	 * file, it may have gone away.
	 */
	if (sp->frp != NULL) {
		sp->frp->lno = sp->lno;
		sp->frp->cno = sp->cno;
		F_SET(sp->frp, FR_CURSORSET);
	}

	/* Switch screens. */
	sp->nextdisp = nsp;
	F_SET(sp, SC_SSWITCH);

	/* Initialize terminal information. */
	VIP(nsp)->srows = VIP(sp)->srows;

	/* Initialize screen information. */
	nsp->cols = sp->cols;
	nsp->rows = sp->rows;	/* XXX: Only place in vi that sets rows. */
	nsp->woff = sp->woff;

	/*
	 * Small screens: see vs_refresh.c, section 6a.
	 *
	 * The new screens may have different screen options sizes than the
	 * old one, so use them.  Make sure that text counts aren't larger
	 * than the new screen sizes.
	 */
	if (IS_SMALL(nsp)) {
		nsp->t_minrows = nsp->t_rows = O_VAL(nsp, O_WINDOW);
		if (nsp->t_rows > sp->t_maxrows)
			nsp->t_rows = nsp->t_maxrows;
		if (nsp->t_minrows > sp->t_maxrows)
			nsp->t_minrows = nsp->t_maxrows;
	} else
		nsp->t_rows = nsp->t_maxrows = nsp->t_minrows = nsp->rows - 1;

	/* Reset the length of the default scroll. */
	nsp->defscroll = nsp->t_maxrows / 2;

	/* Allocate a new screen map. */
	CALLOC_RET(nsp, _HMAP(nsp), SMAP *, SIZE_HMAP(nsp), sizeof(SMAP));
	_TMAP(nsp) = _HMAP(nsp) + (nsp->t_rows - 1);

	/* Fill the map. */
	if (vs_sm_fill(nsp, nsp->lno, P_FILL))
		return (1);

	/*
	 * The new screen replaces the old screen in the parent/child list.
	 * We insert the new screen after the old one.  If we're exiting,
	 * the exit will delete the old one, if we're foregrounding, the fg
	 * code will move the old one to the background queue.
	 */
	CIRCLEQ_REMOVE(&gp->hq, nsp, q);
	CIRCLEQ_INSERT_AFTER(&gp->dq, sp, nsp, q);

	/*
	 * Don't change the screen's cursor information other than to
	 * note that the cursor is wrong.
	 */
	F_SET(VIP(nsp), VIP_CUR_INVALID);

	/* Draw the new screen from scratch, and add a status line. */
	F_SET(nsp, SC_SCR_REDRAW | SC_STATUS);
	return (0);
}

/*
 * vs_resize --
 *	Change the absolute size of the current screen.
 *
 * PUBLIC: int vs_resize(SCR *, long, adj_t);
 */
int
vs_resize(sp, count, adj)
	SCR *sp;
	long count;
	adj_t adj;
{
	GS *gp;
	SCR *g, *s;
	size_t g_off, s_off;

	gp = sp->gp;

	/*
	 * Figure out which screens will grow, which will shrink, and
	 * make sure it's possible.
	 */
	if (count == 0)
		return (0);
	if (adj == A_SET) {
		if (sp->t_maxrows == count)
			return (0);
		if (sp->t_maxrows > count) {
			adj = A_DECREASE;
			count = sp->t_maxrows - count;
		} else {
			adj = A_INCREASE;
			count = count - sp->t_maxrows;
		}
	}

	g_off = s_off = 0;
	if (adj == A_DECREASE) {
		if (count < 0)
			count = -count;
		s = sp;
		if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count)
			goto toosmall;
		if ((g = CIRCLEQ_PREV(sp, q)) == CIRCLEQ_END(&gp->dq)) {
			if ((g = CIRCLEQ_NEXT(sp, q)) == CIRCLEQ_END(&gp->dq))
				goto toobig;
			g_off = -count;
		} else
			s_off = count;
	} else {
		g = sp;
		if ((s = CIRCLEQ_NEXT(sp, q)) != CIRCLEQ_END(&gp->dq))
			if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count)
				s = NULL;
			else
				s_off = count;
		else
			s = NULL;
		if (s == NULL) {
			if ((s = CIRCLEQ_PREV(sp, q)) == CIRCLEQ_END(&gp->dq)) {
toobig:				msgq(sp, M_BERR, adj == A_DECREASE ?
				    "227|The screen cannot shrink" :
				    "228|The screen cannot grow");
				return (1);
			}
			if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count) {
toosmall:			msgq(sp, M_BERR,
				    "226|The screen can only shrink to %d rows",
				    MINIMUM_SCREEN_ROWS);
				return (1);
			}
			g_off = -count;
		}
	}

	/*
	 * Fix up the screens; we could optimize the reformatting of the
	 * screen, but this isn't likely to be a common enough operation
	 * to make it worthwhile.
	 */
	s->rows += -count;
	s->woff += s_off;
	g->rows += count;
	g->woff += g_off;

	g->t_rows += count;
	if (g->t_minrows == g->t_maxrows)
		g->t_minrows += count;
	g->t_maxrows += count;
	_TMAP(g) += count;
	F_SET(g, SC_SCR_REFORMAT | SC_STATUS);

	s->t_rows -= count;
	s->t_maxrows -= count;
	if (s->t_minrows > s->t_maxrows)
		s->t_minrows = s->t_maxrows;
	_TMAP(s) -= count;
	F_SET(s, SC_SCR_REFORMAT | SC_STATUS);

	return (0);
}

/*
 * vs_getbg --
 *	Get the specified background screen, or, if name is NULL, the first
 *	background screen.
 */
static SCR *
vs_getbg(sp, name)
	SCR *sp;
	char *name;
{
	GS *gp;
	SCR *nsp;
	char *p;

	gp = sp->gp;

	/* If name is NULL, return the first background screen on the list. */
	if (name == NULL) {
		nsp = CIRCLEQ_FIRST(&gp->hq);
		return (nsp == (void *)&gp->hq ? NULL : nsp);
	}

	/* Search for a full match. */
	CIRCLEQ_FOREACH(nsp, &gp->hq, q)
		if (!strcmp(nsp->frp->name, name))
			break;
	if (nsp != (void *)&gp->hq)
		return (nsp);

	/* Search for a last-component match. */
	CIRCLEQ_FOREACH(nsp, &gp->hq, q) {
		if ((p = strrchr(nsp->frp->name, '/')) == NULL)
			p = nsp->frp->name;
		else
			++p;
		if (!strcmp(p, name))
			break;
	}
	if (nsp != (void *)&gp->hq)
		return (nsp);

	return (NULL);
}