/* vi:set ts=4 sw=4:
 *
 * VIM - Vi IMproved		by Bram Moolenaar
 *
 * Do ":help uganda"  in Vim to read a list of people who contributed.
 * Do ":help credits" in Vim to see a list of people who contributed.
 */

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "option.h"

static void reset_VIsual __ARGS((void));
static int win_comp_pos __ARGS((void));
static void win_exchange __ARGS((long));
static void win_rotate __ARGS((int, int));
static void win_append __ARGS((WIN *, WIN *));
static void win_remove __ARGS((WIN *));
static void win_new_height __ARGS((WIN *, int));

static WIN		*prevwin = NULL;		/* previous window */

/*
 * all CTRL-W window commands are handled here, called from normal().
 */
	void
do_window(nchar, Prenum)
	int		nchar;
	long	Prenum;
{
	long	Prenum1;
	WIN		*wp;
	char_u	*ptr;
	int		len;
	int		type = -1;
	WIN		*wp2;

	if (Prenum == 0)
		Prenum1 = 1;
	else
		Prenum1 = Prenum;

	switch (nchar)
	{
/* split current window in two parts */
	case 'S':
	case Ctrl('S'):
	case 's':	reset_VIsual();					/* stop Visual mode */
				win_split((int)Prenum, TRUE);
				break;

/* split current window and edit alternate file */
	case K_CCIRCM:
	case '^':
				reset_VIsual();					/* stop Visual mode */
				stuffReadbuff((char_u *)":split #");
				if (Prenum)
					stuffnumReadbuff(Prenum);	/* buffer number */
				stuffcharReadbuff('\n');
				break;

/* open new window */
	case Ctrl('N'):
	case 'n':	reset_VIsual();					/* stop Visual mode */
				stuffcharReadbuff(':');
				if (Prenum)
					stuffnumReadbuff(Prenum);		/* window height */
				stuffReadbuff((char_u *)"new\n");	/* it is cmdline.c */
				break;

/* quit current window */
	case Ctrl('Q'):
	case 'q':	reset_VIsual();					/* stop Visual mode */
				stuffReadbuff((char_u *)":quit\n");	/* it is cmdline.c */
				break;

/* close current window */
	case Ctrl('C'):
	case 'c':	reset_VIsual();					/* stop Visual mode */
				stuffReadbuff((char_u *)":close\n");	/* it is cmdline.c */
				break;

/* close all but current window */
	case Ctrl('O'):
	case 'o':	reset_VIsual();					/* stop Visual mode */
				stuffReadbuff((char_u *)":only\n");	/* it is cmdline.c */
				break;

/* cursor to next window */
	case 'j':
	case K_DOWN:
	case Ctrl('J'):
				for (wp = curwin; wp->w_next != NULL && Prenum1-- > 0;
															wp = wp->w_next)
					;
new_win:
				/*
				 * When jumping to another buffer, stop visual mode
				 * Do this before changing windows so we can yank the
				 * selection into the '"*' register.
				 */
				if (wp->w_buffer != curbuf && VIsual_active)
				{
					end_visual_mode();
					for (wp2 = firstwin; wp2 != NULL; wp2 = wp2->w_next)
						if (wp2->w_buffer == curbuf &&
											wp2->w_redr_type < NOT_VALID)
						{
							wp2->w_redr_type = NOT_VALID;
							redraw_later(NOT_VALID);
						}
				}
				win_enter(wp, TRUE);
				cursupdate();
				break;

/* cursor to next window with wrap around */
	case Ctrl('W'):
	case 'w':
/* cursor to previous window with wrap around */
	case 'W':
				if (lastwin == firstwin)		/* just one window */
					beep_flush();
				else
				{
					if (Prenum)					/* go to specified window */
					{
						for (wp = firstwin; --Prenum > 0; )
						{
							if (wp->w_next == NULL)
								break;
							else
								wp = wp->w_next;
						}
					}
					else
					{
						if (nchar == 'W')			/* go to previous window */
						{
							wp = curwin->w_prev;
							if (wp == NULL)
								wp = lastwin;		/* wrap around */
						}
						else						/* go to next window */
						{
							wp = curwin->w_next;
							if (wp == NULL)
								wp = firstwin;		/* wrap around */
						}
					}
					goto new_win;
				}
				break;

/* cursor to window above */
	case 'k':
	case K_UP:
	case Ctrl('K'):
				for (wp = curwin; wp->w_prev != NULL && Prenum1-- > 0;
															wp = wp->w_prev)
					;
				goto new_win;

/* cursor to top window */
	case 't':
	case Ctrl('T'):
				wp = firstwin;
				goto new_win;

/* cursor to bottom window */
	case 'b':
	case Ctrl('B'):
				wp = lastwin;
				goto new_win;

/* cursor to last accessed (previous) window */
	case 'p':
	case Ctrl('P'):
				if (prevwin == NULL)
					beep_flush();
				else
				{
					wp = prevwin;
					goto new_win;
				}
				break;

/* exchange current and next window */
	case 'x':
	case Ctrl('X'):
				win_exchange(Prenum);
				break;

/* rotate windows downwards */
	case Ctrl('R'):
	case 'r':	reset_VIsual();					/* stop Visual mode */
				win_rotate(FALSE, (int)Prenum1);	/* downwards */
				break;

/* rotate windows upwards */
	case 'R':	reset_VIsual();					/* stop Visual mode */
				win_rotate(TRUE, (int)Prenum1);		/* upwards */
				break;

/* make all windows the same height */
	case '=':	win_equal(NULL, TRUE);
				break;

/* increase current window height */
	case '+':	win_setheight(curwin->w_height + (int)Prenum1);
				break;

/* decrease current window height */
	case '-':	win_setheight(curwin->w_height - (int)Prenum1);
				break;

/* set current window height */
	case Ctrl('_'):
	case '_':	win_setheight(Prenum ? (int)Prenum : 9999);
				break;

/* jump to tag and split window if tag exists */
	case ']':
	case Ctrl(']'):
				reset_VIsual();					/* stop Visual mode */
				postponed_split = TRUE;
				stuffcharReadbuff(Ctrl(']'));
				break;

/* edit file name under cursor in a new window */
	case 'f':
	case Ctrl('F'):
				reset_VIsual();					/* stop Visual mode */
				ptr = file_name_at_cursor(FNAME_MESS|FNAME_HYP|FNAME_EXP);
				if (ptr != NULL)
				{
					setpcmark();
					if (win_split(0, FALSE) == OK)
						(void)do_ecmd(0, ptr, NULL, NULL, (linenr_t)0,
													   p_hid ? ECMD_HIDE : 0);
					vim_free(ptr);
				}
				break;

/* Go to the first occurence of the identifier under cursor along path in a
 * new window -- webb
 */
	case 'i':						/* Go to any match */
	case Ctrl('I'):
				type = FIND_ANY;
				/* FALLTHROUGH */
	case 'd':						/* Go to definition, using p_def */
	case Ctrl('D'):
				if (type == -1)
					type = FIND_DEFINE;

				if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0)
					break;
				find_pattern_in_path(ptr, len, TRUE,
						Prenum == 0 ? TRUE : FALSE, type,
						Prenum1, ACTION_SPLIT, (linenr_t)1, (linenr_t)MAXLNUM);
				curwin->w_set_curswant = TRUE;
				break;

	default:	beep_flush();
				break;
	}
}

	static void
reset_VIsual()
{
	if (VIsual_active)
	{
		end_visual_mode();
		update_curbuf(NOT_VALID);		/* delete the inversion */
	}
}

/*
 * split the current window, implements CTRL-W s and :split
 *
 * new_height is the height for the new window, 0 to make half of current
 * height redraw is TRUE when redraw now
 *
 * return FAIL for failure, OK otherwise
 */
	int
win_split(new_height, redraw)
	int		new_height;
	int		redraw;
{
	WIN			*wp;
	linenr_t	lnum;
	int			h;
	int			i;
	int			need_status;
	int			do_equal = (p_ea && new_height == 0);
	int			needed;
	int			available;
	int			curwin_height;
	
		/* add a status line when p_ls == 1 and splitting the first window */
	if (lastwin == firstwin && p_ls == 1 && curwin->w_status_height == 0)
		need_status = STATUS_HEIGHT;
	else
		need_status = 0;

/*
 * check if we are able to split the current window and compute its height
 */
	available = curwin->w_height;
 	needed = 2 * MIN_ROWS + STATUS_HEIGHT + need_status;
	if (p_ea)
	{
		for (wp = firstwin; wp != NULL; wp = wp->w_next)
			if (wp != curwin)
			{
				available += wp->w_height;
				needed += MIN_ROWS;
			}
	}
 	if (available < needed)
	{
		EMSG(e_noroom);
		return FAIL;
	}
	curwin_height = curwin->w_height;
	if (need_status)
	{
		curwin->w_status_height = STATUS_HEIGHT;
		curwin_height -= STATUS_HEIGHT;
	}
	if (new_height == 0)
		new_height = curwin_height / 2;

	if (new_height > curwin_height - MIN_ROWS - STATUS_HEIGHT)
		new_height = curwin_height - MIN_ROWS - STATUS_HEIGHT;

	if (new_height < MIN_ROWS)
		new_height = MIN_ROWS;

		/* if it doesn't fit in the current window, need win_equal() */
	if (curwin_height - new_height - STATUS_HEIGHT < MIN_ROWS)
		do_equal = TRUE;
/*
 * allocate new window structure and link it in the window list
 */
	if (p_sb)		/* new window below current one */
		wp = win_alloc(curwin);
	else
		wp = win_alloc(curwin->w_prev);
	if (wp == NULL)
		return FAIL;
/*
 * compute the new screen positions
 */
	win_new_height(wp, new_height);
	win_new_height(curwin, curwin_height - (new_height + STATUS_HEIGHT));
	if (p_sb)		/* new window below current one */
	{
		wp->w_winpos = curwin->w_winpos + curwin->w_height + STATUS_HEIGHT;
		wp->w_status_height = curwin->w_status_height;
		curwin->w_status_height = STATUS_HEIGHT;
	}
	else			/* new window above current one */
	{
		wp->w_winpos = curwin->w_winpos;
		wp->w_status_height = STATUS_HEIGHT;
		curwin->w_winpos = wp->w_winpos + wp->w_height + STATUS_HEIGHT;
	}
/*
 * make the contents of the new window the same as the current one
 */
	wp->w_buffer = curbuf;
	curbuf->b_nwindows++;
	wp->w_cursor = curwin->w_cursor;
	wp->w_row = curwin->w_row;
	wp->w_col = curwin->w_col;
	wp->w_virtcol = curwin->w_virtcol;
	wp->w_curswant = curwin->w_curswant;
	wp->w_set_curswant = curwin->w_set_curswant;
	wp->w_empty_rows = curwin->w_empty_rows;
	wp->w_leftcol = curwin->w_leftcol;
	wp->w_pcmark = curwin->w_pcmark;
	wp->w_prev_pcmark = curwin->w_prev_pcmark;
	wp->w_alt_fnum = curwin->w_alt_fnum;

	wp->w_arg_idx = curwin->w_arg_idx;
	/*
	 * copy tagstack and options from existing window
	 */
	for (i = 0; i < curwin->w_tagstacklen; i++)
	{
		wp->w_tagstack[i].fmark = curwin->w_tagstack[i].fmark;
		wp->w_tagstack[i].tagname = strsave(curwin->w_tagstack[i].tagname);
	}
	wp->w_tagstackidx = curwin->w_tagstackidx;
	wp->w_tagstacklen = curwin->w_tagstacklen;
	win_copy_options(curwin, wp);
/*
 * Both windows need redrawing
 */
 	wp->w_redr_type = NOT_VALID;
	wp->w_redr_status = TRUE;
 	curwin->w_redr_type = NOT_VALID;
	curwin->w_redr_status = TRUE;
/*
 * Cursor is put in middle of window in both windows
 */
	if (wp->w_height < curwin->w_height)	/* use smallest of two heights */
		h = wp->w_height;
	else
		h = curwin->w_height;
	h >>= 1;
	for (lnum = wp->w_cursor.lnum; lnum > 1; --lnum)
	{
		h -= plines(lnum);
		if (h <= 0)
			break;
	}
	wp->w_topline = lnum;
	curwin->w_topline = lnum;
	if (need_status)
	{
		msg_pos((int)Rows - 1, sc_col);
		msg_clr_eos();		/* Old command/ruler may still be there -- webb */
		comp_col();
		msg_pos((int)Rows - 1, 0);	/* put position back at start of line */
	}
/*
 * make the new window the current window and redraw
 */
	if (do_equal)
		win_equal(wp, FALSE);
 	win_enter(wp, FALSE);

	if (redraw)
		updateScreen(NOT_VALID);
	else
		redraw_later(NOT_VALID);

	return OK;
}

/*
 * Check if "win" is a pointer to an existing window.
 */
	int
win_valid(win)
	WIN		*win;
{
	WIN 	*wp;

	for (wp = firstwin; wp != NULL; wp = wp->w_next)
		if (wp == win)
			return TRUE;
	return FALSE;
}

/*
 * Return the number of windows.
 */
	int
win_count()
{
	WIN 	*wp;
	int		count = 0;

	for (wp = firstwin; wp != NULL; wp = wp->w_next)
		++count;
	return count;
}

/*
 * Make 'count' windows on the screen.
 * Return actual number of windows on the screen.
 * Must be called when there is just one window, filling the whole screen
 * (excluding the command line).
 */
	int
make_windows(count)
	int		count;
{
	int		maxcount;
	int		todo;
	int		p_sb_save;

/*
 * Each window needs at least MIN_ROWS lines and a status line.
 * Add 4 lines for one window, otherwise we may end up with all one-line
 * windows. Use value of 'winheight' if it is set
 */
	maxcount = (curwin->w_height + curwin->w_status_height -
						(p_wh ? (p_wh - 1) : 4)) / (MIN_ROWS + STATUS_HEIGHT);
	if (maxcount < 2)
		maxcount = 2;
	if (count > maxcount)
		count = maxcount;

	/*
	 * add status line now, otherwise first window will be too big
	 */
	if ((p_ls == 2 || (count > 1 && p_ls == 1)) && curwin->w_status_height == 0)
	{
		curwin->w_status_height = STATUS_HEIGHT;
		win_new_height(curwin, curwin->w_height - STATUS_HEIGHT);
	}

#ifdef AUTOCMD
/*
 * Don't execute autocommands while creating the windows.  Must do that
 * when putting the buffers in the windows.
 */
	++autocmd_busy;
#endif

/*
 * set 'splitbelow' off for a moment, don't want that now
 */
	p_sb_save = p_sb;
	p_sb = FALSE;
		/* todo is number of windows left to create */
	for (todo = count - 1; todo > 0; --todo)
		if (win_split(curwin->w_height - (curwin->w_height - todo
				* STATUS_HEIGHT) / (todo + 1) - STATUS_HEIGHT, FALSE) == FAIL)
			break;
	p_sb = p_sb_save;

#ifdef AUTOCMD
	--autocmd_busy;
#endif

		/* return actual number of windows */
	return (count - todo);
}

/*
 * Exchange current and next window
 */
	static void
win_exchange(Prenum)
	long		Prenum;
{
	WIN		*wp;
	WIN		*wp2;
	int		temp;

	if (lastwin == firstwin)		/* just one window */
	{
		beep_flush();
		return;
	}

/*
 * find window to exchange with
 */
	if (Prenum)
	{
		wp = firstwin;
		while (wp != NULL && --Prenum > 0)
			wp = wp->w_next;
	}
	else if (curwin->w_next != NULL)	/* Swap with next */
		wp = curwin->w_next;
	else	/* Swap last window with previous */
		wp = curwin->w_prev;

	if (wp == curwin || wp == NULL)
		return;

/*
 * 1. remove curwin from the list. Remember after which window it was in wp2
 * 2. insert curwin before wp in the list
 * if wp != wp2
 *    3. remove wp from the list
 *    4. insert wp after wp2
 * 5. exchange the status line height
 */
	wp2 = curwin->w_prev;
	win_remove(curwin);
	win_append(wp->w_prev, curwin);
	if (wp != wp2)
	{
		win_remove(wp);
		win_append(wp2, wp);
	}
	temp = curwin->w_status_height;
	curwin->w_status_height = wp->w_status_height;
	wp->w_status_height = temp;

	win_comp_pos();				/* recompute window positions */

	win_enter(wp, TRUE);
	cursupdate();
	updateScreen(CLEAR);

#ifdef USE_GUI
	if (gui.in_use)
	{
		if (gui.which_scrollbars[SB_LEFT])
			gui_mch_reorder_scrollbars(SB_LEFT);
		if (gui.which_scrollbars[SB_RIGHT])
			gui_mch_reorder_scrollbars(SB_RIGHT);
	}
#endif
}

/*
 * rotate windows: if upwards TRUE the second window becomes the first one
 *				   if upwards FALSE the first window becomes the second one
 */
	static void
win_rotate(upwards, count)
	int		upwards;
	int		count;
{
	WIN			 *wp;
	int			 height;

	if (firstwin == lastwin)			/* nothing to do */
	{
		beep_flush();
		return;
	}
	while (count--)
	{
		if (upwards)			/* first window becomes last window */
		{
			wp = firstwin;
			win_remove(wp);
			win_append(lastwin, wp);
			wp = lastwin->w_prev;			/* previously last window */
		}
		else					/* last window becomes first window */
		{
			wp = lastwin;
			win_remove(lastwin);
			win_append(NULL, wp);
			wp = firstwin;					/* previously last window */
		}
			/* exchange status height of old and new last window */
		height = lastwin->w_status_height;
		lastwin->w_status_height = wp->w_status_height;
		wp->w_status_height = height;

			/* recompute w_winpos for all windows */
		(void) win_comp_pos();
	}

	cursupdate();
	updateScreen(CLEAR);

#ifdef USE_GUI
	if (gui.in_use)
	{
		if (gui.which_scrollbars[SB_LEFT])
			gui_mch_reorder_scrollbars(SB_LEFT);
		if (gui.which_scrollbars[SB_RIGHT])
			gui_mch_reorder_scrollbars(SB_RIGHT);
	}
#endif
}

/*
 * Make all windows the same height.
 * 'next_curwin' will soon be the current window, make sure it has enough
 * rows.
 */
	void
win_equal(next_curwin, redraw)
	WIN		*next_curwin;			/* pointer to current window to be */
	int		redraw;
{
	int		total;
	int		less;
	int		wincount;
	int		winpos;
	int		temp;
	WIN		*wp;
	int		new_height;

/*
 * count the number of lines available
 */
	total = 0;
	wincount = 0;
	for (wp = firstwin; wp; wp = wp->w_next)
	{
		total += wp->w_height - MIN_ROWS;
		wincount++;
	}

/*
 * If next_curwin given and 'winheight' set, make next_curwin p_wh lines.
 */
	less = 0;
	if (next_curwin != NULL)
	{
		if (p_wh)
		{
			if (p_wh - MIN_ROWS > total)	/* all lines go to current window */
				less = total;
			else
			{
				less = p_wh - MIN_ROWS - total / wincount;
				if (less < 0)
					less = 0;
			}
		}
	}

/*
 * spread the available lines over the windows
 */
	winpos = 0;
	for (wp = firstwin; wp != NULL; wp = wp->w_next)
	{
		if (wp == next_curwin && less)
		{
			less = 0;
			temp = p_wh - MIN_ROWS;
			if (temp > total)
				temp = total;
		}
		else
			temp = (total - less + (wincount >> 1)) / wincount;
		new_height = MIN_ROWS + temp;
		if (wp->w_winpos != winpos || wp->w_height != new_height)
		{
			wp->w_redr_type = NOT_VALID;
			wp->w_redr_status = TRUE;
		}
		wp->w_winpos = winpos;
		win_new_height(wp, new_height);
		total -= temp;
		--wincount;
		winpos += wp->w_height + wp->w_status_height;
	}
	if (redraw)
	{
		cursupdate();
		updateScreen(CLEAR);
	}
}

/*
 * close all windows for buffer 'buf'
 */
	void
close_windows(buf)
	BUF		*buf;
{
	WIN 	*win;

	++RedrawingDisabled;
	for (win = firstwin; win != NULL && lastwin != firstwin; )
	{
		if (win->w_buffer == buf)
		{
			close_window(win, FALSE);
			win = firstwin;			/* go back to the start */
		}
		else
			win = win->w_next;
	}
	--RedrawingDisabled;
}

/*
 * close window "win"
 * If "free_buf" is TRUE related buffer may be freed.
 *
 * called by :quit, :close, :xit, :wq and findtag()
 */
	void
close_window(win, free_buf)
	WIN		*win;
	int		free_buf;
{
	WIN 	*wp;
#ifdef AUTOCMD
	int		other_buffer = FALSE;
#endif

	if (lastwin == firstwin)
	{
		EMSG("Cannot close last window");
		return;
	}

#ifdef AUTOCMD
	if (win == curwin)
	{
		/*
		 * Guess which window is going to be the new current window.
		 * This may change because of the autocommands (sigh).
		 */
		if ((!p_sb && win->w_next != NULL) || win->w_prev == NULL)
			wp = win->w_next;
		else
			wp = win->w_prev;

		/*
		 * Be careful: If autocommands delete the window, return now.
		 */
		if (wp->w_buffer != curbuf)
		{
			other_buffer = TRUE;
			apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, FALSE);
			if (!win_valid(win))
				return;
		}
		apply_autocmds(EVENT_WINLEAVE, NULL, NULL, FALSE);
		if (!win_valid(win))
			return;
	}
#endif

/*
 * Remove the window.
 * if 'splitbelow' the free space goes to the window above it.
 * if 'nosplitbelow' the free space goes to the window below it.
 * This makes opening a window and closing it immediately keep the same window
 * layout.
 */
									/* freed space goes to next window */
	if ((!p_sb && win->w_next != NULL) || win->w_prev == NULL)
	{
		wp = win->w_next;
		wp->w_winpos = win->w_winpos;
	}
	else							/* freed space goes to previous window */
		wp = win->w_prev;
	win_new_height(wp, wp->w_height + win->w_height + win->w_status_height);

/*
 * Close the link to the buffer.
 */
	close_buffer(win, win->w_buffer, free_buf, FALSE);

	win_free(win);
	if (win == curwin)
		curwin = NULL;
	if (p_ea)
		win_equal(wp, FALSE);
	if (curwin == NULL)
	{
		win_enter(wp, FALSE);
#ifdef AUTOCMD
		if (other_buffer)
			/* careful: after this wp and win may be invalid! */
			apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE);
#endif
	}

	/*
	 * if last window has status line now and we don't want one,
	 * remove the status line
	 */
	if (lastwin->w_status_height &&
						(p_ls == 0 || (p_ls == 1 && firstwin == lastwin)))
	{
		win_new_height(lastwin, lastwin->w_height + lastwin->w_status_height);
		lastwin->w_status_height = 0;
		comp_col();
	}

	updateScreen(NOT_VALID);
	if (RedrawingDisabled && win_valid(wp))
		comp_Botline(wp);			/* need to do this before cursupdate() */
}

/*
 * close all windows except current one
 * buffers in the windows become hidden
 *
 * called by :only and do_arg_all();
 */
	void
close_others(message)
	int		message;
{
	WIN 	*wp;
	WIN 	*nextwp;

	if (lastwin == firstwin)
	{
		if (message
#ifdef AUTOCMD
					&& !autocmd_busy
#endif
									)
			MSG("Already only one window");
		return;
	}

	for (wp = firstwin; wp != NULL; wp = nextwp)
	{
		nextwp = wp->w_next;
		if (wp == curwin)				/* don't close current window */
			continue;
		/*
		 * Close the link to the buffer.
		 */
		close_buffer(wp, wp->w_buffer, FALSE, FALSE);

		/*
		 * Remove the window. All lines go to current window.
		 */
		win_new_height(curwin,
					   curwin->w_height + wp->w_height + wp->w_status_height);
		win_free(wp);
	}

	/*
	 * If current window has status line and we don't want one,
	 * remove the status line.
	 */
	if (curwin->w_status_height && p_ls != 2)
	{
		win_new_height(curwin, curwin->w_height + curwin->w_status_height);
		curwin->w_status_height = 0;
	}
	curwin->w_winpos = 0;		/* put current window at top of the screen */
	if (message)
		updateScreen(NOT_VALID);
}

/*
 * init the cursor in the window
 *
 * called when a new file is being edited
 */
	void
win_init(wp)
	WIN		*wp;
{
	wp->w_redr_type = NOT_VALID;
	wp->w_cursor.lnum = 1;
	wp->w_curswant = wp->w_cursor.col = 0;
	wp->w_pcmark.lnum = 1;		/* pcmark not cleared but set to line 1 */
	wp->w_pcmark.col = 0;
	wp->w_prev_pcmark.lnum = 0;
	wp->w_prev_pcmark.col = 0;
	wp->w_topline = 1;
	wp->w_botline = 2;
}

/*
 * Make window wp the current window.
 * Can be called when curwin == NULL, if curwin already has been closed.
 */
	void
win_enter(wp, undo_sync)
	WIN		*wp;
	int		undo_sync;
{
#ifdef AUTOCMD
	int			other_buffer = FALSE;
#endif

	if (wp == curwin)			/* nothing to do */
		return;

#ifdef AUTOCMD
	if (curwin != NULL)
	{
		/*
		 * Be careful: If autocommands delete the window, return now.
		 */
		if (wp->w_buffer != curbuf)
		{
			apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, FALSE);
			other_buffer = TRUE;
			if (!win_valid(wp))
				return;
		}
		apply_autocmds(EVENT_WINLEAVE, NULL, NULL, FALSE);
		if (!win_valid(wp))
			return;
	}
#endif

		/* sync undo before leaving the current buffer */
	if (undo_sync && curbuf != wp->w_buffer)
		u_sync();
		/* may have to copy the buffer options when 'cpo' contains 'S' */
	if (wp->w_buffer != curbuf)
		buf_copy_options(curbuf, wp->w_buffer, TRUE, FALSE);
	if (curwin != NULL)
		prevwin = curwin;		/* remember for CTRL-W p */
	curwin = wp;
	curbuf = wp->w_buffer;

#ifdef AUTOCMD
	apply_autocmds(EVENT_WINENTER, NULL, NULL, FALSE);
	if (other_buffer)
		apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE);
#endif

	maketitle();
			/* set window height to desired minimal value */
	if (p_wh && curwin->w_height < p_wh)
		win_setheight((int)p_wh);
#ifdef USE_MOUSE
	setmouse();					/* in case jumped to/from help buffer */
#endif
}

/*
 * allocate a window structure and link it in the window list
 */
	WIN *
win_alloc(after)
	WIN		*after;
{
	WIN		*newwin;

/*
 * allocate window structure and linesizes arrays
 */
	newwin = (WIN *)alloc((unsigned)sizeof(WIN));
	if (newwin)
	{
/*
 * most stucture members have to be zero
 */
 		(void)vim_memset(newwin, 0, sizeof(WIN));
/*
 * link the window in the window list
 */
		win_append(after, newwin);

		win_alloc_lsize(newwin);

		/* position the display and the cursor at the top of the file. */
		newwin->w_topline = 1;
		newwin->w_botline = 2;
		newwin->w_cursor.lnum = 1;

#ifdef USE_GUI
		/* Let the GUI know this is a new scrollbar */
		newwin->w_scrollbar.height = 0;
#endif /* USE_GUI */
	}
	return newwin;
}

/*
 * remove window 'wp' from the window list and free the structure
 */
	void
win_free(wp)
	WIN		*wp;
{
	int		i;

	if (prevwin == wp)
		prevwin = NULL;
	win_free_lsize(wp);

	for (i = 0; i < wp->w_tagstacklen; ++i)
		free(wp->w_tagstack[i].tagname);

#ifdef USE_GUI
	if (gui.in_use)
		gui_mch_destroy_scrollbar(wp);
#endif /* USE_GUI */

	win_remove(wp);
	vim_free(wp);
}

	static void
win_append(after, wp)
	WIN		*after, *wp;
{
	WIN 	*before;

	if (after == NULL)		/* after NULL is in front of the first */
		before = firstwin;
	else
		before = after->w_next;

	wp->w_next = before;
	wp->w_prev = after;
	if (after == NULL)
		firstwin = wp;
	else
		after->w_next = wp;
	if (before == NULL)
		lastwin = wp;
	else
		before->w_prev = wp;
}

/*
 * remove window from the window list
 */
	static void
win_remove(wp)
	WIN		*wp;
{
	if (wp->w_prev)
		wp->w_prev->w_next = wp->w_next;
	else
		firstwin = wp->w_next;
	if (wp->w_next)
		wp->w_next->w_prev = wp->w_prev;
	else
		lastwin = wp->w_prev;
}

/*
 * allocate lsize arrays for a window
 * return FAIL for failure, OK for success
 */
	int
win_alloc_lsize(wp)
	WIN		*wp;
{
	wp->w_lsize_valid = 0;
	wp->w_lsize_lnum = (linenr_t *) malloc((size_t) (Rows * sizeof(linenr_t)));
	wp->w_lsize = (char_u *)malloc((size_t) Rows);
	if (wp->w_lsize_lnum == NULL || wp->w_lsize == NULL)
	{
		win_free_lsize(wp);		/* one of the two may have worked */
		return FAIL;
	}
	return OK;
}

/*
 * free lsize arrays for a window
 */
 	void
win_free_lsize(wp)
	WIN		*wp;
{
	vim_free(wp->w_lsize_lnum);
	vim_free(wp->w_lsize);
	wp->w_lsize_lnum = NULL;
	wp->w_lsize = NULL;
}

/*
 * call this fuction whenever Rows changes value
 */
	void
screen_new_rows()
{
	WIN		*wp;
	int		extra_lines;

	if (firstwin == NULL)		/* not initialized yet */
		return;
/*
 * the number of extra lines is the difference between the position where
 * the command line should be and where it is now
 */
	compute_cmdrow();
	extra_lines = Rows - p_ch - cmdline_row;
	if (extra_lines < 0)						/* reduce windows height */
	{
		for (wp = lastwin; wp; wp = wp->w_prev)
		{
			if (wp->w_height - MIN_ROWS < -extra_lines)
			{
				extra_lines += wp->w_height - MIN_ROWS;
				win_new_height(wp, MIN_ROWS);
			}
			else
			{
				win_new_height(wp, wp->w_height + extra_lines);
				break;
			}
		}
		(void)win_comp_pos();				/* compute w_winpos */
	}
	else if (extra_lines > 0)				/* increase height of last window */
		win_new_height(lastwin, lastwin->w_height + extra_lines);

	compute_cmdrow();

	if (p_ea)
		win_equal(curwin, FALSE);
}

/*
 * update the w_winpos field for all windows
 * returns the row just after the last window
 */
	static int
win_comp_pos()
{
	WIN		*wp;
	int		row;

	row = 0;
	for (wp = firstwin; wp != NULL; wp = wp->w_next)
	{
		if (wp->w_winpos != row)		/* if position changes, redraw */
		{
			wp->w_winpos = row;
			wp->w_redr_type = NOT_VALID;
			wp->w_redr_status = TRUE;
		}
		row += wp->w_height + wp->w_status_height;
	}
	return row;
}

/*
 * set current window height
 */
	void
win_setheight(height)
	int		height;
{
	WIN		*wp;
	int		room;				/* total number of lines available */
	int		take;				/* number of lines taken from other windows */
	int		room_cmdline;		/* lines available from cmdline */
	int		row;
	int		run;

	if (height < MIN_ROWS)		/* need at least some lines */
		height = MIN_ROWS;
/*
 * compute the room we have from all the windows
 */
	room = MIN_ROWS;			/* count the MIN_ROWS for the current window */
	for (wp = firstwin; wp != NULL; wp = wp->w_next)
		room += wp->w_height - MIN_ROWS;
/*
 * compute the room available from the command line
 */
	room_cmdline = Rows - p_ch - cmdline_row;
/*
 * limit new height to the room available
 */
	if (height > room + room_cmdline)		/* can't make it that large */
		height = room + room_cmdline;		/* use all available room */
/*
 * compute the number of lines we will take from the windows (can be negative)
 */
	take = height - curwin->w_height;
	if (take == 0)							/* no change, nothing to do */
		return;

	if (take > 0)
	{
		take -= room_cmdline;				/* use lines from cmdline first */
		if (take < 0)
			take = 0;
	}
/*
 * set the current window to the new height
 */
	win_new_height(curwin, height);

/*
 * First take lines from the windows below the current window.
 * If that is not enough, takes lines from windows above the current window.
 */
	for (run = 0; run < 2; ++run)
	{
		if (run == 0)
			wp = curwin->w_next;		/* 1st run: start with next window */
		else
			wp = curwin->w_prev;		/* 2nd run: start with prev window */
		while (wp != NULL && take != 0)
		{
			if (wp->w_height - take < MIN_ROWS)
			{
				take -= wp->w_height - MIN_ROWS;
				win_new_height(wp, MIN_ROWS);
			}
			else
			{
				win_new_height(wp, wp->w_height - take);
				take = 0;
			}
			if (run == 0)
				wp = wp->w_next;
			else
				wp = wp->w_prev;
		}
	}

/* recompute the window positions */
	row = win_comp_pos();

/*
 * If there is extra space created between the last window and the command line,
 * clear it.
 */
	if (full_screen)
		screen_fill(row, cmdline_row, 0, (int)Columns, ' ', ' ');
	cmdline_row = row;

	updateScreen(NOT_VALID);
}

#ifdef USE_MOUSE
	void
win_drag_status_line(offset)
	int		offset;
{
	WIN		*wp;
	int		room;
	int		row;
	int		up;				/* if TRUE, drag status line up, otherwise down */

	if (offset < 0)
	{
		up = TRUE;
		offset = -offset;
	}
	else
		up = FALSE;

	if (up)	/* drag up */
	{
		room = 0;
		for (wp = curwin; wp != NULL && room < offset; wp = wp->w_prev)
			room += wp->w_height - MIN_ROWS;
		wp = curwin->w_next;				/* put wp at window that grows */
	}
	else	/* drag down */
	{
		/*
		 * Only dragging the last status line can reduce p_ch.
		 */
		room = Rows - cmdline_row;
		if (curwin->w_next == NULL)
			room -= 1;
		else
			room -= p_ch;
		for (wp = curwin->w_next; wp != NULL && room < offset; wp = wp->w_next)
			room += wp->w_height - MIN_ROWS;
		wp = curwin;						/* put wp at window that grows */
	}

	if (room < offset)		/* Not enough room */
		offset = room;		/* Move as far as we can */
	if (offset <= 0)
		return;

	if (wp != NULL)			/* grow window wp by offset lines */
		win_new_height(wp, wp->w_height + offset);

	if (up)
		wp = curwin;				/* current window gets smaller */
	else
		wp = curwin->w_next;		/* next window gets smaller */

	while (wp != NULL && offset > 0)
	{
		if (wp->w_height - offset < MIN_ROWS)
		{
			offset -= wp->w_height - MIN_ROWS;
			win_new_height(wp, MIN_ROWS);
		}
		else
		{
			win_new_height(wp, wp->w_height - offset);
			offset = 0;
		}
		if (up)
			wp = wp->w_prev;
		else
			wp = wp->w_next;
	}
	row = win_comp_pos();
	screen_fill(row, cmdline_row, 0, (int)Columns, ' ', ' ');
	cmdline_row = row;
	p_ch = Rows - cmdline_row;
	updateScreen(NOT_VALID);
	showmode();
}
#endif /* USE_MOUSE */

/*
 * Set new window height.
 */
	static void
win_new_height(wp, height)
	WIN		*wp;
	int		height;
{
	/* should adjust topline to keep cursor at same relative postition */

	wp->w_height = height;
	win_comp_scroll(wp);
	wp->w_redr_type = NOT_VALID;
	wp->w_redr_status = TRUE;
}

	void
win_comp_scroll(wp)
	WIN		*wp;
{
	wp->w_p_scroll = (wp->w_height >> 1);
	if (wp->w_p_scroll == 0)
		wp->w_p_scroll = 1;
}

/*
 * command_height: called whenever p_ch has been changed
 */
	void
command_height(old_p_ch)
	long	old_p_ch;
{
	if (!starting)
	{
		cmdline_row = Rows - p_ch;
		if (p_ch > old_p_ch)				/* p_ch got bigger */
		{
			if (lastwin->w_height - (p_ch - old_p_ch) < MIN_ROWS)
			{
				emsg(e_noroom);
				p_ch = lastwin->w_height - MIN_ROWS + old_p_ch;
			}
			/* clear the lines added to cmdline */
			if (full_screen)
				screen_fill((int)(cmdline_row), (int)Rows, 0,
														(int)Columns, ' ', ' ');
			msg_row = cmdline_row;
		}
		else if (msg_row < cmdline_row)
			msg_row = cmdline_row;
		redraw_cmdline = TRUE;
	}
	win_new_height(lastwin, (int)(lastwin->w_height + old_p_ch - p_ch));
}

	void
last_status()
{
	if (lastwin->w_status_height)
	{
					/* remove status line */
		if (p_ls == 0 || (p_ls == 1 && firstwin == lastwin))
		{
			win_new_height(lastwin, lastwin->w_height + 1);
			lastwin->w_status_height = 0;
		}
	}
	else
	{
					/* add status line */
		if (p_ls == 2 || (p_ls == 1 && firstwin != lastwin))
		{
			if (lastwin->w_height <= MIN_ROWS)		/* can't do it */
				emsg(e_noroom);
			else
			{
				win_new_height(lastwin, lastwin->w_height - 1);
				lastwin->w_status_height = 1;
			}
		}
	}
}

/*
 * file_name_at_cursor()
 *
 * Return the name of the file under (or to the right of) the cursor.  The
 * p_path variable is searched if the file name does not start with '/'.
 * The string returned has been alloc'ed and should be freed by the caller.
 * NULL is returned if the file name or file is not found.
 */
	char_u *
file_name_at_cursor(options)
	int		options;
{
	return get_file_name_in_path(ml_get_curline(),
											   curwin->w_cursor.col, options);
}
/* options:
 * FNAME_MESS		give error messages
 * FNAME_EXP		expand to path
 * FNAME_HYP		check for hypertext link
 */
	char_u *
get_file_name_in_path(ptr, col, options)
	char_u	*ptr;
	int		col;
	int		options;
{
	char_u	*dir;
	char_u	*file_name;
	char_u	save_char;
	char_u	*curr_path = NULL;
	int		curr_path_len;
	int		len;

		/* search forward for what could be the start of a file name */
	while (ptr[col] != NUL && !isfilechar(ptr[col]))
		++col;
	if (ptr[col] == NUL)			/* nothing found */
	{
		if (options & FNAME_MESS)
			EMSG("No file name under cursor");
		return NULL;
	}

		/* search backward for char that cannot be in a file name */
	while (col >= 0 && isfilechar(ptr[col]))
		--col;
	ptr += col + 1;
	col = 0;

		/* search forward for a char that cannot be in a file name */
	while (isfilechar(ptr[col]))
		++col;

	if (options & FNAME_HYP)
	{
		/* For hypertext links, ignore the name of the machine.
		 * Such a link looks like "type://machine/path". Only "/path" is used.
		 * First search for the string "://", then for the extra '/'
		 */
		if ((file_name = vim_strchr(ptr, ':')) != NULL &&
				STRNCMP(file_name, "://", (size_t)3) == 0 &&
				(file_name = vim_strchr(file_name + 3, '/')) != NULL &&
				file_name < ptr + col)
		{
			col -= file_name - ptr;
			ptr = file_name;
			if (ptr[1] == '~')		/* skip '/' for /~user/path */
			{
				++ptr;
				--col;
			}
		}
	}

	if (!(options & FNAME_EXP))
		return strnsave(ptr, col);

		/* copy file name into NameBuff, expanding environment variables */
	save_char = ptr[col];
	ptr[col] = NUL;
	expand_env(ptr, NameBuff, MAXPATHL);
	ptr[col] = save_char;

	if (isFullName(NameBuff))			/* absolute path */
	{
		if ((file_name = strsave(NameBuff)) == NULL)
			return NULL;
		if (getperm(file_name) >= 0)
			return file_name;
		if (options & FNAME_MESS)
		{
			sprintf((char *)IObuff, "Can't find file `%s'", NameBuff);
			emsg(IObuff);
		}
	}
	else							/* relative path, use 'path' option */
	{
		if (curbuf->b_xfilename != NULL)
		{
			curr_path = curbuf->b_xfilename;
			ptr = gettail(curr_path);
			curr_path_len = ptr - curr_path;
		}
		else
			curr_path_len = 0;
		if ((file_name = alloc((int)(curr_path_len + STRLEN(p_path) +
											STRLEN(NameBuff) + 3))) == NULL)
			return NULL;

		for (dir = p_path; *dir;)
		{
			len = copy_option_part(&dir, file_name, 31000, " ,");
			/* len == 0 means: use current directory */
			if (len != 0)
			{
								/* Look for file relative to current file */
				if (file_name[0] == '.' && curr_path_len > 0 &&
										(len == 1 || ispathsep(file_name[1])))
				{
					if (len == 1)		/* just a "." */
						len = 0;
					else				/* "./path": move "path" */
					{
						len -= 2;
						vim_memmove(file_name + curr_path_len, file_name + 2,
																 (size_t)len);
					}
					STRNCPY(file_name, curr_path, curr_path_len);
					len += curr_path_len;
				}
				if (!ispathsep(file_name[len - 1]))
					file_name[len++] = PATHSEP;
			}
			STRCPY(file_name + len, NameBuff);
			if (getperm(file_name) >= 0)
				return file_name;
		}
		if (options & FNAME_MESS)
			EMSG2("Can't find file \"%s\" in path", NameBuff);
	}
	vim_free(file_name);			/* file doesn't exist */
	return NULL;
}

/*
 * Return the minimal number of rows that is needed on the screen to display
 * the current number of windows.
 */
	int
min_rows()
{
	WIN		*wp;
	int		total;

	if (firstwin == NULL)		/* not initialized yet */
		return MIN_ROWS + 1;	/* one window plus command line */

	total = p_ch;		/* count the room for the status line */
	for (wp = firstwin; wp != NULL; wp = wp->w_next)
		total += MIN_ROWS + wp->w_status_height;
	return total;
}

/*
 * Return TRUE if there is only one window, not counting a help window, unless
 * it is the current window.
 */
	int
only_one_window()
{
	int		count = 0;
	WIN		*wp;

	for (wp = firstwin; wp != NULL; wp = wp->w_next)
		if (!wp->w_buffer->b_help || wp == curwin)
			++count;
	return (count <= 1);
}
