/* Miscellaneous support functions (such as tilda expansion).

   Copyright (C) 1993 Sebastiano Vigna

    This file is part of ne, the nice editor.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 1, or (at your option)
    any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

In other words, you are welcome to use, share and improve this program.
You are forbidden to forbid anyone else to use, share and improve
what you give them.   Help stamp out software-hoarding!  */


#include "ne.h"
#include <pwd.h>


/* This is the assert function. It appears that its number of arguments is
different on different ANSI C compilers. Better to redefine it. */

#ifdef DEBUG

void __assert(int x, char *expr, char *file, int line) {
	if (!x) fprintf(stderr, "%s can't be false at line %d of %s!\n", expr, line, file);
}

#endif



/* This function returns a pointer to a tilde-expanded version of the
string pointed to by filename. The string should not be free()ed, since
it is tracked locally. Note that this function can return the same
pointer which is passed, in case no tilde expansion has to be
performed. */


const unsigned char *tilde_expand(const unsigned char *filename) {

	static unsigned char *expanded_filename;

	unsigned char *home_dir, *p;
	struct passwd *passwd = NULL;

	if (!filename) return(NULL);

	if (filename[0] != '~') return(filename);

	if (filename[1] == '/') {
		home_dir = getenv("HOME");
		filename++;
	}
	else {
	
		const unsigned char *s;
		unsigned char *t;
	
		s = filename+1;

		while(*s && *s != '/') s++;

		if (t = malloc(s - filename)) {

			memcpy(t, filename+1, s-filename-1);
			t[s-filename-1] = 0;

			passwd = getpwnam(t);

			free(t);
		}

		if (!passwd) return(filename);

		filename = s;
		home_dir = passwd->pw_dir;
	}

	if (p = realloc(expanded_filename, strlen(filename)+strlen(home_dir)+1)) {
		strcat(strcpy(expanded_filename = p, home_dir), filename);
		return(expanded_filename);
	}

	return(filename);
}



/* Given a pathname, this function returns a pointer to the real file name
(i.e., the pointer points inside the string passed). */


const unsigned char *file_part(const unsigned char *pathname) {

	const unsigned char *p;

	if (!pathname) return(NULL);

	p = pathname+strlen(pathname);

	while(p > pathname && *(p-1) != '/') p--;

	return(p);
}



/* This function duplicates a string. */

char *strdup(const char *s) {

	unsigned char *dup;

	if (!s) return(NULL);

	dup = malloc(strlen(s)+1);

	if (dup) strcpy(dup, s);
	return(dup);
}



/* This function is a useful shortcut for output_chars()ing a
NULL-terminated string. */

void output_string(const unsigned char *s) {

	assert(s != NULL);

	output_chars(s, strlen(s));
}




/* This function is typically passed to qsort() in order to sort an array
of string pointers. */

int strcmpp(const void *a, const void *b) {

	return(strcmp(*(unsigned char **)a, *(unsigned char **)b));
}



/* This function sets the "interactive I/O mode" of the terminal. It
suitably sets the mode bits of the termios structure, and then transmits
various capability strings by calling set_terminal_modes(). This function
assumes that the terminfo database has been properly initialized. The
old_termios structure records the original state of the terminal interface.
Note that if terminal_reset is TRUE, we assume that resetterm() has been
called in the past (likely by unset_interactive_mode()), so we call
fixterm(). Usually, the first invocation of set_interactive_mode() is after
term_init(), which calls setupterm(), so that fixterm() is not necessary. */

#ifndef _AMIGA
static struct termios termios, old_termios;
#endif

static int terminal_reset;


void set_interactive_mode(void) {

	if (terminal_reset) {
		/* This call restores some terminal characteristics which
		are felt necessary by terminfo. */

		fixterm();
		terminal_reset = FALSE;
	}

#ifndef _AMIGA

	tcgetattr(0, &termios);

	old_termios = termios;

	termios.c_iflag &= ~(IXON | IXOFF | ICRNL | INLCR | ISTRIP | IEXTEN);
	termios.c_iflag |= IGNBRK;

	termios.c_oflag &= ~OPOST;

	termios.c_lflag &= ~(ICANON | ECHO);
	termios.c_lflag |= ISIG;

	termios.c_cflag &= ~PARENB;
	termios.c_cflag |= CS8;

	termios.c_cc[VTIME] = 0;
	termios.c_cc[VMIN] = 1;

	/* Now we keep the kernel from intercepting any keyboard input in order
	to turn it into a signal. Note that some signals, such as dsusp on BSD,
	are not trackable	here. They have to be disabled through suitable
	commands (for instance, `stty dsusp ^-'). */

	termios.c_cc[VSUSP] = _POSIX_VDISABLE;
	termios.c_cc[VQUIT] = _POSIX_VDISABLE;
	termios.c_cc[VKILL] = _POSIX_VDISABLE;
	termios.c_cc[VINTR] = _POSIX_VDISABLE;

	/* Control-\ is the stop control sequence for ne. */

	termios.c_cc[VQUIT] =
	termios.c_cc[VINTR] = '\\'-'@';

	tcsetattr(0, TCSADRAIN, &termios);

#endif

	/* We enable the keypad, cursor addressing, etc. */

	set_terminal_modes();

}



/* This function undoes the work of the previous one, in reverse order. It
assumes the old_termios has been filled with the old termios structure. It
sets terminal_reset to TRUE, so that fixterm() will be called on the next
invocation of set_interactive_mode(). */

void unset_interactive_mode(void) {

	/* We move the cursor on the last line, clear it, and output
	a CR, so that the kernel can track the cursor position. Note that
	clear_to_eol() can move the cursor. */

	move_cursor(lines-1, 0);
	clear_to_eol();
	move_cursor(lines-1, 0);
	putchar('\r');

	/* Now we disable the keypad, cursor addressing, etc. fflush()
	guarantees that tcsetattr() won't clip part of the capability
	strings output by reset_terminal_modes(). */

	reset_terminal_modes();
	fflush(stdout);

	/* Now we restore all the flags in the termios structure
	to the state they were before us. */

#ifndef _AMIGA
	tcsetattr(0, TCSANOW, &old_termios);
#endif

	/* Finally, we undo the changes made by the terminfo call setupterm(). */

	resetterm();
	terminal_reset = TRUE;

}


/* This function computes the TAB-expanded length of a line descriptor
up to a certain position. The position can be greater than the line length,
the usual convention of infinite expansion via spaces being in place. */

int calc_len(line_desc *ld, int n, int tab_size) {

	int i, len;

	for(i=len=0; i<n; i++) {
		if (i >= ld->line_len || ld->line[i] != '\t') len++;
		else len += tab_size - len%tab_size;
	}

	return(len);
}


/* This function inverts the computation of calc_len. For a given horizontal
position on a line descriptor, the index of the character "containing" that
position is given. For a non-TAB character, this means that
calc_len(index+1) = n. For a TAB character, calc_len(index+1) >= n, since
the nth horizontal position could fall inside the expansion of the TAB. */

int calc_pos(line_desc *ld, int n, int tab_size) {

	int i, len;

	for(i=len=0; i<ld->line_len && len<n; i++)
		if (ld->line[i] != '\t') len++;
		else len += tab_size - len%tab_size;

	return(i);
}
