ftp.nice.ch/pub/next/unix/editor/vim.3.0.s.tar.gz#/vim-3.0/src/misccmds.c

This is misccmds.c in view mode; [Download] [Up]

/* vi:ts=4:sw=4
 *
 * VIM - Vi IMproved		by Bram Moolenaar
 *
 * Read the file "credits.txt" for a list of people who contributed.
 * Read the file "uganda.txt" for copying and usage conditions.
 */

/*
 * misccmds.c: functions that didn't seem to fit elsewhere
 */

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

static void check_status __ARGS((BUF *));

static char_u *(si_tab[]) = {(char_u *)"if", (char_u *)"else", (char_u *)"while", (char_u *)"for", (char_u *)"do"};

/*
 * count the size of the indent in the current line
 */
	int
get_indent()
{
	register char_u *ptr;
	register int count = 0;

	for (ptr = ml_get(curwin->w_cursor.lnum); *ptr; ++ptr)
	{
		if (*ptr == TAB)	/* count a tab for what it is worth */
			count += (int)curbuf->b_p_ts - (count % (int)curbuf->b_p_ts);
		else if (*ptr == ' ')
			++count;			/* count a space for one */
		else
			break;
	}
	return (count);
}

/*
 * set the indent of the current line
 * leaves the cursor on the first non-blank in the line
 */
	void
set_indent(size, delete)
	register int size;
	int delete;
{
	int				oldstate = State;
	register int	c;

	State = INSERT;		/* don't want REPLACE for State */
	curwin->w_cursor.col = 0;
	if (delete)							/* delete old indent */
	{
		while ((c = gchar_cursor()), iswhite(c))
			(void)delchar(FALSE);
	}
	if (!curbuf->b_p_et)			/* if 'expandtab' is set, don't use TABs */
		while (size >= (int)curbuf->b_p_ts)
		{
			inschar(TAB);
			size -= (int)curbuf->b_p_ts;
		}
	while (size)
	{
		inschar(' ');
		--size;
	}
	State = oldstate;
}

/*
 * Opencmd
 *
 * Add a blank line below or above the current line.
 *
 * Return TRUE for success, FALSE for failure
 */

	int
Opencmd(dir, redraw, delspaces)
	int 		dir;
	int			redraw;
	int			delspaces;
{
	char_u   *ptr, *p_extra;
	FPOS	old_cursor; 			/* old cursor position */
	int		newcol = 0;			/* new cursor column */
	int 	newindent = 0;		/* auto-indent of the new line */
	int		n;
	int		truncate = FALSE;	/* truncate current line afterwards */
	int		no_si = FALSE;		/* reset did_si afterwards */
	int		retval = FALSE;		/* return value, default is FAIL */

	ptr = strsave(ml_get(curwin->w_cursor.lnum));
	if (ptr == NULL)			/* out of memory! */
		return FALSE;

	u_clearline();				/* cannot do "U" command when adding lines */
	did_si = FALSE;
	if (curbuf->b_p_ai || curbuf->b_p_si)
	{
		/*
		 * count white space on current line
		 */
		newindent = get_indent();
		if (newindent == 0)
			newindent = old_indent;		/* for ^^D command in insert mode */
		old_indent = 0;

			/*
			 * If we just did an auto-indent, then we didn't type anything on the
			 * prior line, and it should be truncated.
			 */
		if (dir == FORWARD && did_ai)
			truncate = TRUE;
		else if (curbuf->b_p_si && *ptr != NUL)
		{
			char_u	*p;
			char_u	*pp;
			int		i, save;

			if (dir == FORWARD)
			{
				p = ptr + STRLEN(ptr) - 1;
				while (p > ptr && isspace(*p))	/* find last non-blank in line */
					--p;
				if (*p == '{')					/* line ends in '{': do indent */
				{
					did_si = TRUE;
					no_si = TRUE;
				}
				else							/* look for "if" and the like */
				{
					p = ptr;
					skipspace(&p);
					for (pp = p; islower(*pp); ++pp)
						;
					if (!isidchar(*pp))			/* careful for vars starting with "if" */
					{
						save = *pp;
						*pp = NUL;
						for (i = sizeof(si_tab)/sizeof(char_u *); --i >= 0; )
							if (STRCMP(p, si_tab[i]) == 0)
							{
								did_si = TRUE;
								break;
							}
						*pp = save;
					}
				}
			}
			else
			{
				p = ptr;
				skipspace(&p);
				if (*p == '}')			/* if line starts with '}': do indent */
					did_si = TRUE;
			}
		}
		did_ai = TRUE;
		if (curbuf->b_p_si)
			can_si = TRUE;
	}
	if (State == INSERT || State == REPLACE)	/* only when dir == FORWARD */
	{
		p_extra = ptr + curwin->w_cursor.col;
		if (curbuf->b_p_ai && delspaces)
			skipspace(&p_extra);
		if (*p_extra != NUL)
			did_ai = FALSE; 		/* append some text, don't trucate now */
	}
	else
		p_extra = (char_u *)"";				/* append empty line */

	old_cursor = curwin->w_cursor;
	if (dir == BACKWARD)
		--curwin->w_cursor.lnum;
	if (ml_append(curwin->w_cursor.lnum, p_extra, (colnr_t)0, FALSE) == FAIL)
		goto theend;
	mark_adjust(curwin->w_cursor.lnum + 1, MAXLNUM, 1L);
	if (newindent || did_si)
	{
		++curwin->w_cursor.lnum;
		if (did_si)
		{
			if (p_sr)
				newindent -= newindent % (int)curbuf->b_p_sw;
			newindent += (int)curbuf->b_p_sw;
		}
		set_indent(newindent, FALSE);
		newcol = curwin->w_cursor.col;
		if (no_si)
			did_si = FALSE;
	}
	curwin->w_cursor = old_cursor;

	if (dir == FORWARD)
	{
		if (truncate || State == INSERT || State == REPLACE)
		{
			if (truncate)
				*ptr = NUL;
			else
				*(ptr + curwin->w_cursor.col) = NUL;	/* truncate current line at cursor */
			ml_replace(curwin->w_cursor.lnum, ptr, FALSE);
			ptr = NULL;
		}

		/*
		 * Get the cursor to the start of the line, so that 'curwin->w_row' gets
		 * set to the right physical line number for the stuff that
		 * follows...
		 */
		curwin->w_cursor.col = 0;

		if (redraw)
		{
			n = RedrawingDisabled;
			RedrawingDisabled = TRUE;
			cursupdate();				/* don't want it to update srceen */
			RedrawingDisabled = n;

			/*
			 * If we're doing an open on the last logical line, then go ahead and
			 * scroll the screen up. Otherwise, just insert a blank line at the
			 * right place. We use calls to plines() in case the cursor is
			 * resting on a long line.
			 */
			n = curwin->w_row + plines(curwin->w_cursor.lnum);
			if (n == curwin->w_height)
				scrollup(1L);
			else
				win_ins_lines(curwin, n, 1, TRUE, TRUE);
		}
		++curwin->w_cursor.lnum;	/* cursor moves down */
	}
	else if (redraw) 				/* insert physical line above current line */
		win_ins_lines(curwin, curwin->w_row, 1, TRUE, TRUE);

	curwin->w_cursor.col = newcol;
	if (redraw)
	{
		updateScreen(VALID_TO_CURSCHAR);
		cursupdate();			/* update curwin->w_row */
	}
	CHANGED;

	retval = TRUE;				/* success! */
theend:
	free(ptr);
	return retval;
}

/*
 * plines(p) - return the number of physical screen lines taken by line 'p'
 */
	int
plines(p)
	linenr_t	p;
{
	return plines_win(curwin, p);
}
	
	int
plines_win(wp, p)
	WIN			*wp;
	linenr_t	p;
{
	register long		col = 0;
	register char_u		*s;
	register int		lines;

	if (!wp->w_p_wrap)
		return 1;

	s = ml_get_buf(wp->w_buffer, p, FALSE);
	if (*s == NUL)				/* empty line */
		return 1;

	while (*s != NUL)
		col += chartabsize(*s++, col);

	/*
	 * If list mode is on, then the '$' at the end of the line takes up one
	 * extra column.
	 */
	if (wp->w_p_list)
		col += 1;

	/*
	 * If 'number' mode is on, add another 8.
	 */
	if (wp->w_p_nu)
		col += 8;

	lines = (col + (Columns - 1)) / Columns;
	if (lines <= wp->w_height)
		return lines;
	return (int)(wp->w_height);		/* maximum length */
}

/*
 * Count the physical lines (rows) for the lines "first" to "last" inclusive.
 */
	int
plines_m(first, last)
	linenr_t		first, last;
{
	return plines_m_win(curwin, first, last);
}

	int
plines_m_win(wp, first, last)
	WIN				*wp;
	linenr_t		first, last;
{
	int count = 0;

	while (first <= last)
		count += plines_win(wp, first++);
	return (count);
}

/*
 * insert or replace a single character at the cursor position
 */
	void
inschar(c)
	int			c;
{
	register char_u  *p;
	int				rir0;		/* reverse replace in column 0 */
	char_u			*new;
	char_u			*old;
	int				oldlen;
	int				extra;
	colnr_t			col = curwin->w_cursor.col;
	linenr_t		lnum = curwin->w_cursor.lnum;

	old = ml_get(lnum);
	oldlen = STRLEN(old) + 1;

	rir0 = (State == REPLACE && p_ri && col == 0);
	if (rir0 || State != REPLACE || *(old + col) == NUL)
		extra = 1;
	else
		extra = 0;

	new = alloc((unsigned)(oldlen + extra));
	if (new == NULL)
		return;
	memmove((char *)new, (char *)old, (size_t)col);
	p = new + col;
	memmove((char *)p + extra, (char *)old + col, (size_t)(oldlen - col));
	if (rir0)					/* reverse replace in column 0 */
	{
		*(p + 1) = c;			/* replace the char that was in column 0 */
		c = ' ';				/* insert a space */
		extraspace = TRUE;
	}
	*p = c;
	ml_replace(lnum, new, FALSE);

	/*
	 * If we're in insert mode and showmatch mode is set, then check for
	 * right parens and braces. If there isn't a match, then beep. If there
	 * is a match AND it's on the screen, then flash to it briefly. If it
	 * isn't on the screen, don't do anything.
	 */
	if (p_sm && State == INSERT && (c == ')' || c == '}' || c == ']'))
	{
		FPOS		   *lpos, csave;

		if ((lpos = showmatch(NUL)) == NULL)		/* no match, so beep */
			beep();
		else if (lpos->lnum >= curwin->w_topline)
		{
			updateScreen(VALID_TO_CURSCHAR); /* show the new char first */
			csave = curwin->w_cursor;
			curwin->w_cursor = *lpos; 	/* move to matching char */
			cursupdate();
			showruler(0);
			setcursor();
			cursor_on();		/* make sure that the cursor is shown */
			flushbuf();
			vim_delay();		/* brief pause */
			curwin->w_cursor = csave; 	/* restore cursor position */
			cursupdate();
		}
	}
	if (!p_ri)							/* normal insert: cursor right */
		++curwin->w_cursor.col;
	else if (State == REPLACE && !rir0)	/* reverse replace mode: cursor left */
		--curwin->w_cursor.col;
	CHANGED;
}

/*
 * insert a string at the cursor position
 */
	void
insstr(s)
	register char_u  *s;
{
	register char_u		*old, *new;
	register int		newlen = STRLEN(s);
	int					oldlen;
	colnr_t				col = curwin->w_cursor.col;
	linenr_t			lnum = curwin->w_cursor.lnum;

	old = ml_get(lnum);
	oldlen = STRLEN(old);
	new = alloc((unsigned)(oldlen + newlen + 1));
	if (new == NULL)
		return;
	memmove((char *)new, (char *)old, (size_t)col);
	memmove((char *)new + col, (char *)s, (size_t)newlen);
	memmove((char *)new + col + newlen, (char *)old + col, (size_t)(oldlen - col + 1));
	ml_replace(lnum, new, FALSE);
	curwin->w_cursor.col += newlen;
	CHANGED;
}

/*
 * delete one character under the cursor
 *
 * return FAIL for failure, OK otherwise
 */
	int
delchar(fixpos)
	int			fixpos; 	/* if TRUE fix the cursor position when done */
{
	char_u		*old, *new;
	int			oldlen;
	linenr_t	lnum = curwin->w_cursor.lnum;
	colnr_t		col = curwin->w_cursor.col;
	int			was_alloced;

	old = ml_get(lnum);
	oldlen = STRLEN(old);

	if (col >= oldlen)	/* can't do anything (happens with replace mode) */
		return FAIL;

/*
 * If the old line has been allocated the deleteion can be done in the
 * existing line. Otherwise a new line has to be allocated
 */
	was_alloced = ml_line_alloced();		/* check if old was allocated */
	if (was_alloced)
		new = old;							/* use same allocated memory */
	else
	{
		new = alloc((unsigned)oldlen);		/* need to allocated a new line */
		if (new == NULL)
			return FAIL;
		memmove((char *)new, (char *)old, (size_t)col);
	}
	memmove((char *)new + col, (char *)old + col + 1, (size_t)(oldlen - col));
	if (!was_alloced)
		ml_replace(lnum, new, FALSE);

	/*
	 * If we just took off the last character of a non-blank line, we don't
	 * want to end up positioned at the newline.
	 */
	if (fixpos && curwin->w_cursor.col > 0 && col == oldlen - 1)
		--curwin->w_cursor.col;

	CHANGED;
	return OK;
}

	void
dellines(nlines, dowindow, undo)
	long 			nlines;			/* number of lines to delete */
	int 			dowindow;		/* if true, update the window */
	int				undo;			/* if true, prepare for undo */
{
	int 			num_plines = 0;

	if (nlines <= 0)
		return;
	/*
	 * There's no point in keeping the window updated if we're deleting more
	 * than a window's worth of lines.
	 */
	if (nlines > (curwin->w_height - curwin->w_row) && dowindow)
	{
		dowindow = FALSE;
		/* flaky way to clear rest of window */
		win_del_lines(curwin, curwin->w_row, curwin->w_height, TRUE, TRUE);
	}
	if (undo && !u_savedel(curwin->w_cursor.lnum, nlines))
		return;

	mark_adjust(curwin->w_cursor.lnum, curwin->w_cursor.lnum + nlines - 1, MAXLNUM);
	mark_adjust(curwin->w_cursor.lnum + nlines, MAXLNUM, -nlines);

	while (nlines-- > 0)
	{
		if (bufempty()) 		/* nothing to delete */
			break;

		/*
		 * Set up to delete the correct number of physical lines on the
		 * window
		 */
		if (dowindow)
			num_plines += plines(curwin->w_cursor.lnum);

		ml_delete(curwin->w_cursor.lnum);

		CHANGED;

		/* If we delete the last line in the file, stop */
		if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count)
		{
			curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
			break;
		}
	}
	curwin->w_cursor.col = 0;
	/*
	 * Delete the correct number of physical lines on the window
	 */
	if (dowindow && num_plines > 0)
		win_del_lines(curwin, curwin->w_row, num_plines, TRUE, TRUE);
}

	int
gchar(pos)
	FPOS *pos;
{
	return (int)(*(ml_get_pos(pos)));
}

	int
gchar_cursor()
{
	return (int)(*(ml_get_cursor()));
}

/*
 * Write a character at the current cursor position.
 * It is directly written into the block.
 */
	void
pchar_cursor(c)
	int c;
{
	*(ml_get_buf(curbuf, curwin->w_cursor.lnum, TRUE) + curwin->w_cursor.col) = c;
}

/*
 * return TRUE if the cursor is before or on the first non-blank in the line
 */
	int
inindent()
{
	register char_u *ptr;
	register int col;

	for (col = 0, ptr = ml_get(curwin->w_cursor.lnum); iswhite(*ptr); ++col)
		++ptr;
	if (col >= curwin->w_cursor.col)
		return TRUE;
	else
		return FALSE;
}

/*
 * skipspace: skip over ' ' and '\t'.
 *
 * note: you must give a pointer to a char_u pointer!
 */
	void
skipspace(pp)
	char_u **pp;
{
    register char_u *p;
    
    for (p = *pp; *p == ' ' || *p == '\t'; ++p)	/* skip to next non-white */
    	;
    *pp = p;
}

/*
 * skiptospace: skip over text until ' ' or '\t'.
 *
 * note: you must give a pointer to a char_u pointer!
 */
	void
skiptospace(pp)
	char_u **pp;
{
	register char_u *p;

	for (p = *pp; *p != ' ' && *p != '\t' && *p != NUL; ++p)
		;
	*pp = p;
}

/*
 * skiptodigit: skip over text until digit found
 *
 * note: you must give a pointer to a char_u pointer!
 */
	void
skiptodigit(pp)
	char_u **pp;
{
	register char_u *p;

	for (p = *pp; !isdigit(*p) && *p != NUL; ++p)
		;
	*pp = p;
}

/*
 * getdigits: get a number from a string and skip over it
 *
 * note: you must give a pointer to a char_u pointer!
 */

	long
getdigits(pp)
	char_u **pp;
{
    register char_u *p;
	long retval;
    
	p = *pp;
	retval = atol((char *)p);
    while (isdigit(*p))	/* skip to next non-digit */
    	++p;
    *pp = p;
	return retval;
}

	char_u *
plural(n)
	long n;
{
	static char_u buf[2] = "s";

	if (n == 1)
		return &(buf[1]);
	return &(buf[0]);
}

/*
 * set_Changed is called when something in the current buffer is changed
 */
	void
set_Changed()
{
	if (!curbuf->b_changed)
	{
		change_warning();
		curbuf->b_changed = TRUE;
		check_status(curbuf);
	}
}

/*
 * unset_Changed is called when the changed flag must be reset for buffer 'buf'
 */
	void
unset_Changed(buf)
	BUF		*buf;
{
	if (buf->b_changed)
	{
		buf->b_changed = 0;
		check_status(buf);
	}
}

/*
 * check_status: called when the status bars for the buffer 'buf'
 *				 need to be updated
 */
	static void
check_status(buf)
	BUF		*buf;
{
	WIN		*wp;
	int		i;

	i = 0;
	for (wp = firstwin; wp != NULL; wp = wp->w_next)
		if (wp->w_buffer == buf && wp->w_status_height)
		{
			wp->w_redr_status = TRUE;
			++i;
		}
	if (i && must_redraw < NOT_VALID)		/* redraw later */
		must_redraw = NOT_VALID;
}

/*
 * If the file is readonly, give a warning message with the first change.
 * Don't use emsg(), because it flushes the macro buffer.
 * If we have undone all changes b_changed will be FALSE, but b_did_warn
 * will be TRUE.
 */
	void
change_warning()
{
	if (curbuf->b_did_warn == FALSE && curbuf->b_changed == 0 && curbuf->b_p_ro)
	{
		curbuf->b_did_warn = TRUE;
		MSG("Warning: Changing a readonly file");
		sleep(1);			/* give him some time to think about it */
	}
}

/*
 * ask for a reply from the user, a 'y' or a 'n'.
 * No other characters are accepted, the message is repeated until a valid
 * reply is entered or CTRL-C is hit.
 *
 * return the 'y' or 'n'
 */
	int
ask_yesno(str)
	char_u *str;
{
	int r = ' ';

	while (r != 'y' && r != 'n')
	{
		(void)set_highlight('r');		/* same highlighting as for wait_return */
		msg_highlight = TRUE;
		smsg((char_u *)"%s (y/n)?", str);
		r = vgetc();
		if (r == Ctrl('C'))
			r = 'n';
		msg_outchar(r);		/* show what you typed */
		flushbuf();
	}
	return r;
}

	void
msgmore(n)
	long n;
{
	long pn;

	if (global_busy)		/* no messages now, wait until global is finished */
		return;

	if (n > 0)
		pn = n;
	else
		pn = -n;

	if (pn > p_report)
		smsg((char_u *)"%ld %s line%s %s", pn, n > 0 ? "more" : "fewer", plural(pn),
											got_int ? "(Interrupted)" : "");
}

/*
 * give a warning for an error
 */
	void
beep()
{
	flush_buffers(FALSE);		/* flush internal buffers */
	if (p_vb)
	{
		if (T_VB && *T_VB)
		    outstr(T_VB);
		else
		{						/* very primitive visual bell */
	        MSG("    ^G");
	        MSG("     ^G");
	        MSG("    ^G ");
	        MSG("     ^G");
	        MSG("       ");
			showmode();			/* may have deleted the mode message */
		}
	}
	else
	    outchar('\007');
}

/* 
 * Expand environment variable with path name.
 * "~/" is also expanded, like $HOME.
 * If anything fails no expansion is done and dst equals src.
 */
	void
expand_env(src, dst, dstlen)
	char_u	*src;			/* input string e.g. "$HOME/vim.hlp" */
	char_u	*dst;			/* where to put the result */
	int		dstlen;			/* maximum length of the result */
{
	char_u	*tail;
	int		c;
	char_u	*var;

	if (*src == '$' || (*src == '~' && STRCHR("/ \t\n", src[1]) != NULL))
	{
/*
 * The variable name is copied into dst temporarily, because it may be
 * a string in read-only memory.
 */
		if (*src == '$')
		{
			tail = src + 1;
			var = dst;
			c = dstlen - 1;
			while (c-- > 0 && *tail && isidchar(*tail))
				*var++ = *tail++;
			*var = NUL;
/*
 * It is possible that vimgetenv() uses IObuff for the expansion, and that the
 * 'dst' is also IObuff. This works, as long as 'var' is the first to be copied
 * to 'dst'!
 */
			var = vimgetenv(dst);
		}
		else
		{
			var = vimgetenv((char_u *)"HOME");
			tail = src + 1;
		}
		if (var && (STRLEN(var) + STRLEN(tail) + 1 < (unsigned)dstlen))
		{
			STRCPY(dst, var);
			STRCAT(dst, tail);
			return;
		}
	}
	STRNCPY(dst, src, (size_t)dstlen);
}

/* 
 * Replace home directory by "~/"
 * If anything fails dst equals src.
 */
	void
home_replace(src, dst, dstlen)
	char_u	*src;			/* input file name */
	char_u	*dst;			/* where to put the result */
	int		dstlen;			/* maximum length of the result */
{
	char_u	*home;
	size_t	len;

	/*
	 * If there is no "HOME" environment variable, or when it
	 * is very short, don't replace anything.
	 */
	if ((home = vimgetenv((char_u *)"HOME")) == NULL || (len = STRLEN(home)) <= 1)
		STRNCPY(dst, src, (size_t)dstlen);
	else
	{
		skipspace(&src);
		while (*src && dstlen > 0)
		{
			if (STRNCMP(src, home, len) == 0)
			{
				src += len;
				if (--dstlen > 0)
					*dst++ = '~';
			}
			while (*src && *src != ' ' && --dstlen > 0)
				*dst++ = *src++;
			while (*src == ' ' && --dstlen > 0)
				*dst++ = *src++;
		}
		*dst = NUL;
	}
}

/*
 * Compare two file names and return TRUE if they are different files.
 * For the first name environment variables are expanded
 */
	int
fullpathcmp(s1, s2)
	char_u *s1, *s2;
{
#ifdef UNIX
	struct stat st1, st2;
	char_u buf1[MAXPATHL];

	expand_env(s1, buf1, MAXPATHL);
	if (stat((char *)buf1, &st1) == 0 && stat((char *)s2, &st2) == 0 &&
				st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino)
		return FALSE;
	return TRUE;
#else
	char_u buf1[MAXPATHL];
	char_u buf2[MAXPATHL];

	expand_env(s1, buf2, MAXPATHL);
	if (FullName(buf2, buf1, MAXPATHL) == OK && FullName(s2, buf2, MAXPATHL) == OK)
		return STRCMP(buf1, buf2);
	/*
	 * one of the FullNames() failed, file probably doesn't exist.
	 */
	return TRUE;
#endif
}

/*
 * get the tail of a path: the file name.
 */
	char_u *
gettail(fname)
	char_u *fname;
{
	register char_u *p1, *p2;

	for (p1 = p2 = fname; *p2; ++p2)	/* find last part of path */
	{
		if (ispathsep(*p2))
			p1 = p2 + 1;
	}
	return p1;
}

/*
 * return TRUE if 'c' is a path separator.
 */
	int
ispathsep(c)
	int c;
{
#ifdef UNIX
	return (c == PATHSEP);		/* UNIX has ':' inside file names */
#else
# ifdef MSDOS
	return (c == ':' || c == PATHSEP || c == '/');
# else
	return (c == ':' || c == PATHSEP);
# endif
#endif
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.