/*======================================================================*\
|*		Editor mined						*|
|*		file handling functions					*|
\*======================================================================*/

#include "mined.h"
#include "textfile.h"
#include "termprop.h"	/* utf8_screen */

#include <sys/stat.h>
#include <errno.h>


/*======================================================================*\
|*			Data section					*|
\*======================================================================*/

#define mark_file	"@mined.mar"
#define new_mark_file	"@mined.$$$"

static int open_linum;			/* line # to re-position to */
static int open_col;			/* line column to re-position to */
static int open_pos;			/* character index to re-position to */

int fnami;			/* Parameter index of current file name */
int fnami_min, fnami_max;
char * * fnamv;			/* Copy of argv, points to program params */

FLAG overwriteOK = False;	/* Set if current file is OK for overwrite */
char * default_text_encoding = "";

static FLAG save_viewonly;
static FLAG save_restricted;
static char save_file_name [maxLINE_LEN];
static int save_cur_pos;
static int save_cur_line;

FLAG viewing_help = False;


/* options */
static FLAG multiexit = True;	/* Should exit command go to next file? */
FLAG init_viewonly = False;	/* Set with option v */
char * preselect_quote_style = NIL_PTR;

FLAG lineends_LFtoCRLF = False;
FLAG lineends_CRLFtoLF = False;
FLAG lineends_CRtoLF = False;
FLAG lineends_detectCR = False;


/*======================================================================*\
|*			File loading performance debugging		*|
\*======================================================================*/

#define dont_debug_timing

#ifdef debug_timing

#include <sys/time.h>

static
long
gettime ()
{
  struct timeval now;
  gettimeofday (& now, 0);
  return ((long) now.tv_sec) * 1000000 + now.tv_usec;
}

#define mark_time(timer)	timer -= gettime ()
#define elapsed_time(timer)	timer += gettime ()
#define elapsed_mark_time(timer1, timer2)	{long t = gettime (); timer1 += t; timer2 -= t;}

#else

#define mark_time(timer)
#define elapsed_time(timer)
#define elapsed_mark_time(timer1, timer2)

#endif


/*======================================================================*\
|*	UTF-8 character statistics and quote style detection		*|
\*======================================================================*/

/**
   determine if current position (if quote mark) is opening
 */
static
FLAG
isopeningquote (s, beg)
  char * s;
  char * beg;
{
	unsigned int prevchar = precedingchar (s, beg);
	/* for now, follow simplified approach; don't consider 
	   quotes after quotes, or CJK embedded quotes */
	switch (prevchar) {
	case '(':
	case '\n':
	case '\t':
	case ' ':
	case '[':
	case '{':
		return True;
	}
	return False;
}

/**
   language-specific quotation mark counters
 */
static unsigned long count_plain = 0;
static unsigned long count_English = 0;
static unsigned long count_German = 0;
static unsigned long count_French = 0;
static unsigned long count_inwards = 0;
static unsigned long count_Dutch = 0;
static unsigned long count_Swedish_q = 0;
static unsigned long count_Swedish_g = 0;
static unsigned long count_Greek = 0;
static unsigned long count_Chinese = 0;
static unsigned long count_Japanese = 0;

static
void
reset_quote_statistics ()
{
	count_plain = 0;
	count_English = 0;
	count_German = 0;
	count_French = 0;
	count_inwards = 0;
	count_Dutch = 0;
	count_Swedish_q = 0;
	count_Swedish_g = 0;
	count_Greek = 0;
	count_Chinese = 0;
	count_Japanese = 0;
}

static unsigned long count_quotes;

static
void
check_quote_style (c, s)
  unsigned long c;
  char * s;
{
/*printf ("%4d %s\n", c, s);*/
  if (c > count_quotes) {
	count_quotes = c;
	set_quote_style (s);
  }
}

static
void
determine_quote_style ()
{
	count_quotes = 0;
	check_quote_style (count_plain, "\"\"");
	check_quote_style (count_English, "“”");
	check_quote_style (count_inwards, "»«");
	check_quote_style (count_German, "„“");
	check_quote_style (count_French, "«» ‹›");
	check_quote_style (count_Dutch, "„”");
	check_quote_style (count_Swedish_q, "””");
	check_quote_style (count_Swedish_g, "»»");
	check_quote_style (count_Greek, "«» ‟”");
	check_quote_style (count_Chinese, "《》");
	check_quote_style (count_Japanese, "『』");
}

/*
 * utf8_count () returns the number of UTF-8 characters in the string
   (like char_count) and also detects quotation marks and updates 
   their statistics.
 */
static
int
utf8_count (string)
  char * string;
{
  char * start = string;
  int count = 0;
  unsigned long unichar;
  int utflen;

  if (string != NIL_PTR) {
    while (* string != '\0') {
	/* Detect quotation marks.
	   The UTF-8 codes of all quotation marks are either 
	   C2AB or C2BB or start with either E280 or E380. 
	   This may help for efficient detection during file loading.
	 */
	if ((((character) * string) <= 0x27
	     && (* string == '\'' || * string == '"')
	    )
	    ||
	    ((* string & 0xDE) == 0xC2
	     && (((character) * string) == 0xC2 || ((character) * (string + 1)) == 0x80)
	    )
	   )
	{
	    utf8_info (string, & utflen, & unichar);
	    switch ((unsigned int) unichar) {
	    case (character) '"':
	    case (character) '\'':
			count_plain ++;
			break;
	    case 0x201C: /* “ LEFT DOUBLE QUOTATION MARK; DOUBLE TURNED COMMA QUOTATION MARK */
	    case 0x2018: /* ‘ LEFT SINGLE QUOTATION MARK; SINGLE TURNED COMMA QUOTATION MARK */
			/* left English, Spanish, Turkish */
			/* right German, Danish, Polish, Russian, Romanian, Slovak, Slovenian, Czech, Hungarian */
		if (isopeningquote (string, start)) {
			count_English ++;
		} else {
			count_German ++;
		}
		break;
	    case 0x201D: /* ” RIGHT DOUBLE QUOTATION MARK; DOUBLE COMMA QUOTATION MARK */
		count_Greek ++;
	    case 0x2019: /* ’ RIGHT SINGLE QUOTATION MARK; SINGLE COMMA QUOTATION MARK */
			/* right English, Spanish, Turkish */
			/* right Dutch, Hungarian */
			/* left/right Swedish, Finnish */
			/* ” right nested traditional Greek */
		count_Swedish_q ++;
		if (! isopeningquote (string, start)) {
			count_English ++;
			count_Dutch ++;
		}
		break;
	    case 0x201E: /* „ DOUBLE LOW-9 QUOTATION MARK; LOW DOUBLE COMMA QUOTATION MARK */
	    case 0x201A: /* ‚ SINGLE LOW-9 QUOTATION MARK; LOW SINGLE COMMA QUOTATION MARK */
			/* left German, Danish, Polish, Russian, Romanian, Slovak, Sloven, Czech, Hungarian */
			/* left Dutch, Hungarian */
		if (isopeningquote (string, start)) {
			count_German ++;
			count_Dutch ++;
		}
		break;
	    case 0x201F: /* ‟ DOUBLE HIGH-REVERSED-9 QUOTATION MARK; DOUBLE REVERSED COMMA QUOTATION MARK */
		count_Greek ++;
	    case 0x201B: /* ‛ SINGLE HIGH-REVERSED-9 QUOTATION MARK; SINGLE REVERSED COMMA QUOTATION MARK */
			/* ‟ left nested traditional Greek */
		break;
	    case 0x00AB: /* « LEFT-POINTING DOUBLE ANGLE QUOTATION MARK; LEFT POINTING GUILLEMET */
	    case 0x2039: /* ‹ SINGLE LEFT-POINTING ANGLE QUOTATION MARK; LEFT POINTING SINGLE GUILLEMET */
			/* left French, Italian, Norwegian, Portuguese, Russian, Slovenian, Turkish */
			/* right German, Polish, Slovak, Czech, Serbian, Croatian */
			/* left Greek */
		if (isopeningquote (string, start)) {
			count_French ++;
			count_Greek ++;
		} else {
			count_German ++;
			count_inwards ++;
		}
		break;
	    case 0x00BB: /* » RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK; RIGHT POINTING GUILLEMET */
	    case 0x203A: /* › SINGLE RIGHT-POINTING ANGLE QUOTATION MARK; RIGHT POINTING SINGLE GUILLEMET */
			/* right French, Italian, Norwegian, Portuguese, Russian, Slovenian, Turkish */
			/* left German, Polish, Slovak, Czech, Serbian, Croatian */
			/* left/right Swedish, Finnish */
			/* right Greek */
		count_Swedish_g ++;
		if (isopeningquote (string, start)) {
			count_German ++;
			count_inwards ++;
		} else {
			count_French ++;
			count_Greek ++;
		}
		break;
	    case 0x300A: /* 《 LEFT DOUBLE ANGLE BRACKET; OPENING DOUBLE ANGLE BRACKET */
	    case 0x3008: /* 〈 LEFT ANGLE BRACKET; OPENING ANGLE BRACKET */
			/* left Chinese? */
			/* 〈 left Chinese?? */
		if (isopeningquote (string, start)) {
			count_Chinese ++;
		}
		break;
	    case 0x300B: /* 》 RIGHT DOUBLE ANGLE BRACKET; CLOSING DOUBLE ANGLE BRACKET */
	    case 0x3009: /* 〉 RIGHT ANGLE BRACKET; CLOSING ANGLE BRACKET */
			/* right Chinese? */
			/* 〉 right Chinese?? */
		if (! isopeningquote (string, start)) {
			count_Chinese ++;
		}
		break;
	    case 0x300C: /* 「 LEFT CORNER BRACKET; OPENING CORNER BRACKET */
	    case 0x300E: /* 『 LEFT WHITE CORNER BRACKET; OPENING WHITE CORNER BRACKET */
			/* left Japanese */
			/* 『 left Japanese? */
		if (isopeningquote (string, start)) {
			count_Japanese ++;
		}
		break;
	    case 0x300D: /* 」 RIGHT CORNER BRACKET; CLOSING CORNER BRACKET */
	    case 0x300F: /* 』 RIGHT WHITE CORNER BRACKET; CLOSING WHITE CORNER BRACKET */
			/* right Japanese */
			/* 』 right Japanese? */
		if (! isopeningquote (string, start)) {
			count_Japanese ++;
		}
		break;
	    }
	}

	/* Advance and count */
	advance_utf8 (& string);
	count ++;
    }
  }
  return count;
}


/*======================================================================*\
|*			File I/O buffer					*|
\*======================================================================*/

#define filebuflen (10 * 1024)
static char filebuf [filebuflen + 1];
static unsigned int filebuf_count = 0;

void clear_filebuf ()
{
  filebuf_count = 0;
}


/*======================================================================*\
|*			File position handling				*|
\*======================================================================*/

/*
 * get_open_pos and save_open_pos look up and save the current file 
   position in @mined.mar
   For save_open_pos, line_number must be up-to-date
 */
static
void
get_open_pos (fn)
  char * fn;
{
  int mark_fd = open (mark_file, O_RDONLY | O_BINARY, 0);
  if (mark_fd >= 0) {
	FLAG modif = modified;
	FLAG get_quote_type = quote_type == 0;
	int dumlen;
	int fnlen = strlen (fn);

	reset_get_line (False);
	open_linum = 0;
	open_col = 0;
	open_pos = 0;

	while ((get_line (mark_fd, text_buffer, & dumlen, False)) != ERRORS) {
	    if (strncmp (fn, text_buffer, fnlen) == 0) {
		char * spoi = text_buffer + fnlen;
		if (* spoi == ' ') {
			int v4, v5, v6 = -1;
			lines_per_page = 0;
			spoi = scan_int (spoi, & open_linum);
			spoi = scan_int (spoi, & open_col);
			if (open_col < 0) {
				/* indicates new character index semantics */
				open_pos = - open_col;
			}
			spoi = scan_int (spoi, & lines_per_page);
			spoi = scan_int (spoi, & v4);
			if (v4 >= 0) {
				JUSlevel = 1;
				spoi = scan_int (spoi, & v5);
				spoi = scan_int (spoi, & v6);
				if (v6 > 0) {
					first_left_margin = v4;
					next_left_margin = v5;
					right_margin = v6;
				}
			} else {
				JUSlevel = 0;
			}
			if (get_quote_type) {
				int vq = -1;
				spoi = scan_int (spoi, & vq);
				if (vq >= 0) {
					/* legacy int entries */
					if (vq == 1) {
						set_quote_style ("“” ‘’");
					} else if (vq == 2) {
						set_quote_style ("„“ ‚‘");
					} else if (vq == 3) {
						set_quote_style ("«» ‹›");
					} else if (vq == 4) {
						set_quote_style ("»« ›‹");
					} else if (vq == 5) {
						set_quote_style ("„” ‚’");
					} else if (vq == 6) {
						set_quote_style ("”” ’’");
					} else if (vq == 7) {
						set_quote_style ("»» ››");
					} else if (vq == 8) {
						set_quote_style ("『』 「」");
					} else {
						set_quote_type (0);
					}
				} else {
					char * npoi;
					/* string entries */
					while (* spoi == ' ') {
						spoi ++;
					}
					npoi = strchr (spoi, '\n');
					if (npoi != NIL_PTR) {
						* npoi = '\0';
					}
					set_quote_style (spoi);
				}
			}
		}
	    }
	}
	(void) close (mark_fd);
	clear_filebuf ();

	/* prevent affecting the modified flag when loading the line number file */
	modified = modif;
  }
}

/**
   Write file position and other information to mined marker file.
   Return False if writing to the file fails.
 */
static
FLAG
write_open_pos (fd, fn)
  int fd;
  char * fn;
{
	int cur_pos = get_cur_pos ();
	char marktext [maxLINE_LEN];

	if (JUSlevel > 0) {
	    build_string (marktext, "%s %d %d %d %d %d %d %s\n",
			  fn, line_number, - cur_pos, lines_per_page, 
			  first_left_margin, next_left_margin, right_margin, 
			  quote_mark (quote_type, 0));
	} else {
	    build_string (marktext, "%s %d %d %d -3 %s\n",
			  fn, line_number, - cur_pos, lines_per_page, 
			  quote_mark (quote_type, 0));
	}
	if (write (fd, marktext, strlen (marktext)) <= 0) {
		return False;
	} else {
		return True;
	}
}


#ifdef pc
#define append_minedmar
#endif

#ifdef append_minedmar

/**
   Append file position and other information to mined marker file.
   Return False if saving to the file fails.
 */
static
FLAG
append_open_pos (fn, force)
  char * fn;
  int force;
{
  int mark_fd;
  if (force > 0) {
	mark_fd = open (mark_file, O_WRONLY | O_APPEND | O_CREAT, fprot0);
  } else {
	mark_fd = open (mark_file, O_WRONLY | O_APPEND, fprot0);
  }

  if (mark_fd >= 0) {
	FLAG ret = write_open_pos (mark_fd, fn);
	if (close (mark_fd) < 0) {
		ret = False;
	}
	return ret;
  } else {
	return False;
  }
}

#else

#ifdef update_in_place

#define file_pos(fd)	lseek (fd, 0, SEEK_CUR)

/**
   Update file position and other information in mined marker file, 
   doing the modification in place, 
   using two opened file handles to the same file;
   - This does not work on djgpp and is not used thus.
   Housekeeping: clean up older entries for same file.
   Return False if saving to the file fails.
 */
static
FLAG
update_open_pos (fn, force)
  char * fn;
  int force;
{
  int mark_in;
  int mark_out;
  FLAG ret = True;

  mark_in = open (mark_file, O_RDONLY);
  if (mark_in < 0 && ! force) {
	return False;
  }

  mark_out = open (mark_file, O_WRONLY | O_CREAT, fprot0);
  if (mark_out < 0) {
	return False;
  }

  if (mark_in >= 0) {
	/* scan mark file for old entries for same file and skip them;
	   copy other entries to output stream (to same file)
	 */
	int dumlen;
	int fnlen = strlen (fn);
	reset_get_line (False);	/* disable UTF-16 detection */
	while ((get_line (mark_in, text_buffer, & dumlen, False)) != ERRORS) {
		if (strncmp (fn, text_buffer, fnlen) == 0 && text_buffer [fnlen] == ' ') {
			/* skip entry for this filename */
		} else {
			if (write (mark_out, text_buffer, strlen (text_buffer)) <= 0) {
				/* ignore error */
			}
		}
	}
	(void) close (mark_in);
	clear_filebuf ();
  }

  ret = write_open_pos (mark_out, fn);
  ftruncate (mark_out, file_pos (mark_out));
  if (close (mark_out) < 0) {
	ret = False;
  }
  return ret;
}

#else

/**
   Update file position and other information in mined marker file, 
   using two files and finally moving the new one to the old one.
   Housekeeping: clean up older entries for same file.
   Return False if saving to the file fails.
 */
static
FLAG
update_open_pos (fn, force)
  char * fn;
  int force;
{
  int mark_in;
  int mark_out;
  FLAG ret = True;

  mark_in = open (mark_file, O_RDONLY);
  if (mark_in < 0 && ! force) {
	return False;
  }

  mark_out = open (new_mark_file, O_WRONLY | O_TRUNC | O_BINARY | O_CREAT, fprot0);
  if (mark_out < 0) {
	return False;
  }

  if (mark_in >= 0) {
	/* scan mark file for old entries for same file and skip them;
	   copy other entries to output file
	 */
	int dumlen;
	int fnlen = strlen (fn);
	reset_get_line (False);	/* disable UTF-16 detection */
	while ((get_line (mark_in, text_buffer, & dumlen, False)) != ERRORS) {
		if (strncmp (fn, text_buffer, fnlen) == 0 && text_buffer [fnlen] == ' ') {
			/* skip entry for this filename */
		} else {
			if (write (mark_out, text_buffer, strlen (text_buffer)) <= 0) {
				/* ignore error */
			}
		}
	}
	(void) close (mark_in);
	clear_filebuf ();
  }

  ret = write_open_pos (mark_out, fn);
  if (close (mark_out) < 0) {
	ret = False;
  } else {
	if (rename (new_mark_file, mark_file) < 0) {
		/* This yields ENOMEM with djgpp */
		ret = False;
	}
  }
  return ret;
}

#endif

#endif

/**
   Save file position and other information in mined marker file.
   Return False if saving to the file fails.
 */
static
FLAG
save_open_pos (fn, force)
  char * fn;
  int force;
{
  if (fn [0] != '\0') {
#ifdef append_minedmar
	return append_open_pos (fn, force);
#else
	return update_open_pos (fn, force);
#endif
  } else {
	return True;
  }
}


/*======================================================================*\
|*			Auxiliary functions				*|
\*======================================================================*/

static
int
strcaseeq (s1, s2)
  char * s1;
  char * s2;
{
  char c1, c2;
  do {
	if (! * s1 && ! * s2) {
		return True;
	}
	c1 = * s1;
	if (c1 >= 'a' && c1 <= 'z') {
		c1 = c1 - 'a' + 'A';
	}
	c2 = * s2;
	if (c2 >= 'a' && c2 <= 'z') {
		c2 = c2 - 'a' + 'A';
	}
	if (c1 != c2) {
		return False;
	}
	s1 ++;
	s2 ++;
  } while (True);
}


/*
   Set flags depending on file type.
 */
static
void
set_file_type_flags ()
{
  char * suffix = strrchr (file_name, '.');
  if (suffix != NIL_PTR) {
	suffix ++;
  } else {
	suffix = "";
  }
  if (	   strcaseeq (suffix, "html")
	|| strcaseeq (suffix, "htm")
	|| strcaseeq (suffix, "xhtml")
	|| strcaseeq (suffix, "shtml")
	|| strcaseeq (suffix, "sgml")
	|| strcaseeq (suffix, "xml")
	|| strcaseeq (suffix, "xul")
	|| strcaseeq (suffix, "jsp")
	|| strcaseeq (suffix, "asp")
	|| strcaseeq (suffix, "wsdl")
	|| strcaseeq (suffix, "dtd")
	|| strcaseeq (suffix, "xsl")
	|| strcaseeq (suffix, "xslt")
     )
  {
	dim_HTML = True;
  } else {
	dim_HTML = False;
  }
}

/*
 * Initialize is called when a another file is edited. It free's the allocated 
 * space and sets modified back to False and fixes the header/tail pointer.
 */
static
void
initialize ()
{
  register LINE * line;
  register LINE * next_line;

/* Delete the whole list */
  for (line = header->next; line != tail; line = next_line) {
	next_line = line->next;
	free_space (line->text);
	free_header (line);
  }

/* header and tail should point to itself */
  line->next = line->prev = line;
  x = y = 0;
  reading_pipe = modified = False;
}

char *
basename (s)
  char * s;
{
  char * b = strrchr (s, '/');
  if (b == NIL_PTR) {
	return s;
  } else {
	b ++;
	return b;
  }
}


/*======================================================================*\
|*			File operations					*|
\*======================================================================*/

/*
 * get_line reads one line from filedescriptor fd. If EOF is reached on fd,
 * get_line () returns ERRORS, else it returns the length of the string.
 */
static char * get_l_err_u1;
static char * get_l_err1;

static long count_good_utf;	/* count good UTF-8 sequences */
static long count_bad_utf;	/* count bad UTF-8 sequences */
static long count_utf_bytes;	/* count UTF-8 sequence bytes */
static long count_good_iso;	/* count good ISO-8859 bytes */
static long count_good_cp1252;	/* count good CP1252 (Windows Western) bytes */
static long count_good_cp850;	/* count good CP850 (DOS) bytes */
static long count_good_mac;	/* count good MacRoman bytes */
static long count_good_viscii;	/* count good VISCII bytes */
static long count_good_tcvn;	/* count good TCVN bytes */
static long count_1read_op;	/* count 1st read operation by get_line */
static long count_lineend_LF;	/* count Unix lines */
static long count_lineend_CRLF;	/* count MSDOS lines */
static long count_lineend_CR;	/* count Mac lines */
static FLAG BOM;		/* BOM found at beginning of file? */
static FLAG consider_utf16;	/* consider UTF-16 when reading file? */

/*
   CJK character encoding auto-detection
 */
static character last_cjkbyte = '\0';
static character last2_cjkbyte = '\0';
static long count_good_cjk;	/* count good CJK codes */
static long count_weak_cjk;	/* count weak (unsure) CJK codes */
static long count_bad_cjk;	/* count bad CJK codes */
static long count_big5;		/* count Big5 codes */
static long count_gb;		/* count GB (GB2312, GBK, GB18030) codes */
static int detect_gb18030 = 0;	/* observe GB18030 byte state */
static long count_uhc;		/* count UHC (KS C 5601/KS X 1001) codes */
static long count_jp;		/* count JIS (EUC-JP) codes */
static long count_sjis;		/* count Shift-JIS codes */
static long count_sjis1;	/* count Shift-JIS single-byte codes */
static long count_johab;	/* count Johab codes */
static long count_cns;		/* count CNS codes */

#define dont_debug_auto_detect
#define dont_debug_read

void
reset_get_line (from_text_file)
  FLAG from_text_file;	/* consider UTF-16 ? */
{
  get_l_err1 = NIL_PTR;
  get_l_err_u1 = NIL_PTR;

  count_good_utf = 0;
  count_bad_utf = 0;
  count_utf_bytes = 0;

  count_good_cjk = 0;
  count_weak_cjk = 0;
  count_bad_cjk = 0;
  last_cjkbyte = '\0';
  last2_cjkbyte = '\0';

  count_good_iso = 0;
  count_good_viscii = 0;
  count_good_tcvn = 0;
  count_good_cp1252 = 0;
  count_good_cp850 = 0;
  count_good_mac = 0;
  count_big5 = 0;
  count_gb = 0;
  count_uhc = 0;
  count_jp = 0;
  count_sjis = 0;
  count_sjis1 = 0;
  count_johab = 0;
  count_cns = 0;

  count_lineend_LF = 0;
  count_lineend_CRLF = 0;
  count_lineend_CR = 0;

  count_1read_op = 0;

  reset_quote_statistics ();

  BOM = False;
  consider_utf16 = from_text_file;
  utf16_file = False;	/* make sure set_text_encoding (default_text_encoding)
			   is called after this ! */
}

static
void
show_get_l_error (get_l_err)
  char * get_l_err;
{
  if (get_l_err != NIL_PTR) {
	ring_bell ();
	error2 (get_l_err, " -- type a blank");
	while (readcharacter () != ' ' && quit == False) {
		ring_bell ();
		flush ();
	}
  }
}

void
show_get_l_errors ()
{
  if (! only_detect_text_encoding) {
	show_get_l_error (get_l_err1);
	show_get_l_error (get_l_err_u1);
	clear_status ();
  }
}

static unsigned int surrogate = 0;

/*
   Transform UTF-16 input into UTF-8.
 */
static
int
UTF16_transform (UTF8buf, maxbufl, next_byte_poi, fini_byte)
  char * UTF8buf;
  int maxbufl;
  character * * next_byte_poi;
  character * fini_byte;
{
  register char * ptr = UTF8buf;
  int read_chars = 0;
  unsigned long unichar;

  while (read_chars + 4 < maxbufl && * next_byte_poi < fini_byte) {
	unichar = * * next_byte_poi;
	(* next_byte_poi) ++;
	if (* next_byte_poi < fini_byte) {
		if (utf16_little_endian) {
			unichar |= (* * next_byte_poi) << 8;
		} else {
			unichar = (unichar << 8) | (* * next_byte_poi);
		}
		(* next_byte_poi) ++;
	} else if (utf16_little_endian == False) {
		unichar = 0;
	}

	if ((unichar & 0xFC00) == 0xD800) {
	/* high surrogates */
		surrogate = (unichar - 0xD7C0) << 10;
	} else if ((unichar & 0xFC00) == 0xDC00) {
	/* low surrogates */
		unichar = surrogate | (unichar & 0x03FF);
		surrogate = 0;
		* ptr ++ = 0xF0 | (unichar >> 18);
		* ptr ++ = 0x80 | ((unichar >> 12) & 0x3F);
		* ptr ++ = 0x80 | ((unichar >> 6) & 0x3F);
		* ptr ++ = 0x80 | (unichar & 0x3F);
		read_chars += 4;
	} else if (unichar < 0x80) {
		* ptr ++ = unichar;
		read_chars ++;
	} else if (unichar < 0x800) {
		* ptr ++ = 0xC0 | (unichar >> 6);
		* ptr ++ = 0x80 | (unichar & 0x3F);
		read_chars += 2;
	} else {
		* ptr ++ = 0xE0 | (unichar >> 12);
		* ptr ++ = 0x80 | ((unichar >> 6) & 0x3F);
		* ptr ++ = 0x80 | (unichar & 0x3F);
		read_chars += 3;
	}
  }

  return read_chars;
}

#ifdef debug_read
#define trace_read(params)	printf params
#else
#define trace_read(params)	
#endif

static char * last_bufpos = NIL_PTR;
static char * current_bufpos = NIL_PTR;
static char * UTF16buf = NIL_PTR;
static char * fini_byte = NIL_PTR;
static char * next_byte = NIL_PTR;
static long read_bytes;
static long read_chars;

static
void
clear_get_line ()
{
  last_bufpos = NIL_PTR;
  current_bufpos = NIL_PTR;
}

static
FLAG
alloc_UTF16buf ()
{
	if (UTF16buf == NIL_PTR) {
		UTF16buf = alloc (filebuflen);
		if (UTF16buf == NIL_PTR) {
			get_l_err_u1 = "Cannot allocate memory for UTF-16 buffer";
			viewonly = True;
			modified = False;
			return ERRORS;
		}
	}
	return FINE;
}


/**
   Tables supporting auto-detection of some 8-bit character encodings;
   for each character >= 0x80 they indicate whether the character is 
   a letter (cl), a valid character (cv), or invalid (nc).
 */

#define cl 2	/* count weight of letter */
#define cc 1	/* count weight of non-letter */
#define nc -3	/* count weight of invalid character */

static char good_iso [0x80] = {
/*80*/	nc, nc, nc, nc, nc, nc, nc, nc, nc, nc, nc, nc, nc, nc, nc, nc, 
/*90*/	nc, nc, nc, nc, nc, nc, nc, nc, nc, nc, nc, nc, nc, nc, nc, nc, 
/*A0*/	cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, 
/*B0*/	cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, 
/*C0*/	cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, 
/*D0*/	cl, cl, cl, cl, cl, cl, cl, cc, cl, cl, cl, cl, cl, cl, cl, cl, 
/*E0*/	cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, 
/*F0*/	cl, cl, cl, cl, cl, cl, cl, cc, cl, cl, cl, cl, cl, cl, cl, cl, 
};

static char good_cp1252 [0x80] = {
/*80*/	cc, nc, cc, cc, cc, cc, cc, cc, cc, cc, cl, cc, cl, nc, cl, nc, 
/*90*/	nc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cl, cc, cl, nc, cl, cl, 
/*A0*/	cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, 
/*B0*/	cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, 
/*C0*/	cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, 
/*D0*/	cl, cl, cl, cl, cl, cl, cl, cc, cl, cl, cl, cl, cl, cl, cl, cl, 
/*E0*/	cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, 
/*F0*/	cl, cl, cl, cl, cl, cl, cl, cc, cl, cl, cl, cl, cl, cl, cl, cl, 
};

static char good_cp850 [0x80] = {
/*80*/	cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, 
/*90*/	cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cc, cl, cc, cc, 
/*A0*/	cl, cl, cl, cl, cl, cl, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, 
/*B0*/	cc, cc, cc, cc, cc, cl, cl, cl, cc, cc, cc, cc, cc, cc, cc, cc, 
/*C0*/	cc, cc, cc, cc, cc, cc, cl, cl, cc, cc, cc, cc, cc, cc, cc, cc, 
/*D0*/	cl, cl, cl, cl, cl, cl, cl, cl, cl, cc, cc, cc, cc, cc, cl, cc, 
/*E0*/	cl, cl, cl, cl, cl, cl, cc, cl, cl, cl, cl, cl, cl, cl, cc, cc, 
/*F0*/	cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, 
};

static char good_mac [0x80] = {
/*80*/	cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, 
/*90*/	cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, 
/*A0*/	cc, cc, cc, cc, cc, cc, cc, cl, cc, cc, cc, cc, cc, cc, cl, cl, 
/*B0*/	cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cl, cl, 
/*C0*/	cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, cl, cl, cl, cl, cl, 
/*D0*/	cc, cc, cc, cc, cc, cc, cc, cc, cl, cl, cc, cc, cc, cc, cc, cc, 
/*E0*/	cc, cc, cc, cc, cc, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, cl, 
/*F0*/	nc, cl, cl, cl, cl, cl, cc, cc, cc, cc, cc, cc, cc, cc, cc, cc, 
};

static
void auto_detect_byte (curbyte, do_auto_detect)
  character curbyte;
  FLAG do_auto_detect;
{
	/* begin character encoding auto-detection */

	/* UTF-8 auto-detection */
#define dont_debug_utf_detect

#ifdef debug_utf_detect
	printf ("count_utf_bytes %d cur %02X\n", count_utf_bytes, curbyte);
#endif

	if (count_utf_bytes == 0) {
		if ((curbyte & 0xC0) == 0x80) {
			count_bad_utf ++;
		} else {
			count_utf_bytes = UTF8_len (curbyte) - 1;
		}
	} else if ((curbyte & 0xC0) == 0x80) {
		count_utf_bytes --;
		if (count_utf_bytes == 0) {
			count_good_utf ++;
		}
	} else {
		count_utf_bytes = 0;
		count_bad_utf ++;
	}

	/* VISCII and ISO-8859 auto-detection */
	if (curbyte >= 0x80) {

	  if (do_auto_detect) {
		count_good_viscii += cl;
		count_good_tcvn += cl;

		count_good_iso += good_iso [curbyte - 0x80];
		count_good_cp1252 += good_cp1252 [curbyte - 0x80];
		count_good_cp850 += good_cp850 [curbyte - 0x80];
		count_good_mac += good_mac [curbyte - 0x80];
#ifdef debug_auto_detect
		printf ("%02X -> iso %d viscii %d\n", curbyte, count_good_iso, count_good_viscii);
#endif
	  } else {
		/* Defending ISO-8859 vs. CJK auto-detection */
		count_good_iso += good_iso [curbyte - 0x80];
	  }

	} else if (do_auto_detect) {
		switch (curbyte) {
			case '':
			case '':
			case '':
			case '':
				count_good_viscii += 2 * cl;
				count_good_tcvn += 2 * cl;
				break;

			case '':
			case '':
				count_good_viscii += 2 * cl;
				count_good_tcvn += nc;
				break;

			case '':
			case '':
			case '':
			case '':
			case '':
			case '':
			case '':
			case '':
				count_good_viscii += nc;
				count_good_tcvn += 2 * cl;
				break;

			case '\000':
			case '':
			case '':
			case '':
			case '':
			case '':
			case '':
			case '':
			case '':
			case '':
			case 0x1A:
			case '':
			case '':
			case '':
			case '':
				count_good_viscii += nc;
				count_good_tcvn += nc;
				break;
		}
	}

	/* CJK/Han encoding auto-detection */
	/* maintain GB18030 detection state */
	if (detect_gb18030 != 0) {
		detect_gb18030 --;
	}

	/* perform detection on bytes after non-ASCII bytes */
	if (last_cjkbyte >= 0x80) {
	  if (curbyte >= 0x80) {
			if (curbyte != 0xA0 || last_cjkbyte != 0xA0) {
				count_good_cjk += cl;
			}
	  } else if (curbyte < 0x30) {
			count_bad_cjk += cl;
	  } else if (curbyte <= 0x39) {
			if (detect_gb18030 == 1) {
				count_good_cjk += cl;
			}
	  } else {
			count_weak_cjk ++;
	  }

	  /* detect specific CJK encoding */
	  if (do_auto_detect && ! utf16_file) {

		/* CJK character set ranges
			Big5	Big5-HKSCS	87-FE	40-7E, A1-FE
			GB	GBK		81-FE	40-7E, 80-FE
				GB18030		81-FE	30-39	81-FE	30-39
			UHC	UHC		81-FE	41-5A, 61-7A, 81-FE
			JIS	EUC-JP		8E	A1-DF
						A1-FE	A1-FE
						8F	A1-FE	A1-FE
			JIS	Shift-JIS	A1-DF
						81-9F, E0-EF	40-7E, 80-FC
			CNS	EUC-TW		A1-FE	A1-FE
						8E	A1-A7	A1-FE	A1-FE
		*/

	   if (last_cjkbyte >= 0x81 && last_cjkbyte <= 0xFE) {

		/*	Big5	Big5-HKSCS	87-FE	40-7E, A1-FE
		*/
		if (last_cjkbyte >= 0x87 /* && last_cjkbyte <= 0xFE */
		    && ((curbyte >= 0x40 && curbyte <= 0x7E)
		        || (curbyte >= 0xA1 && curbyte <= 0xFE)))
		{
			count_big5 ++;
		}

		/*	GB	GBK		81-FE	40-7E, 80-FE
				GB18030		81-FE	30-39	81-FE	30-39
			UHC	UHC		81-FE	41-5A, 61-7A, 81-FE
		*/
		/* if (last_cjkbyte >= 0x81 && last_cjkbyte <= 0xFE) { */
		    if ((curbyte >= 0x40 && curbyte <= 0x7E)
		        || (curbyte >= 0x80 && curbyte <= 0xFE)) {
			count_gb ++;

			if ((curbyte >= 0x41 && curbyte <= 0x5A)
			        || (curbyte >= 0x61 && curbyte <= 0x7A)
			        || (curbyte >= 0x81 /* && curbyte <= 0xFE */))
			{
				count_uhc ++;
			}

		    } else if (curbyte >= 0x30 && curbyte <= 0x39) {
			if (detect_gb18030 == 1) {
				count_gb += 5;
			} else {
				detect_gb18030 = 3;
			}
		    }
		/* } */

		/*	JIS	EUC-JP		A1-FE	A1-FE
						8F	A1-FE	A1-FE
						8E	A1-DF
		*/
		if ((last_cjkbyte >= 0xA1 && last_cjkbyte <= 0xFE)
		    && curbyte >= 0xA1 && curbyte <= 0xFE)
		{
			if (last2_cjkbyte == 0x8F) {
				count_jp += 3;
			} else {
				count_jp ++;
			}
		}
		if (last_cjkbyte == 0x8E
		    && curbyte >= 0xA1 && curbyte <= 0xFE)
		{
			count_jp += 3;
		}

		/*	JIS	Shift-JIS	A1-DF
						81-9F, E0-EF	40-7E, 80-FC
		*/
		if (last_cjkbyte >= 0xA1 && last_cjkbyte <= 0xDF) {
		    if (curbyte >= 0xA1 && curbyte <= 0xDF) {
			/* two consecutive single-byte SJIS characters */
			count_sjis += 1;
			count_sjis1 ++;
		    }
		} else {
		    if (curbyte >= 0x40 && curbyte <= 0xFC && curbyte != 0x7F) {
			count_sjis ++;
		    }
		}

		/*	Johab	Johab		84-DE, E0-F9	31-7E, 81-FE
		*/
		if (((last_cjkbyte >= 0x84 && last_cjkbyte <= 0xDE)
		        || (last_cjkbyte >= 0xE0 && last_cjkbyte <= 0xF9))
		    && ((curbyte >= 0x31 && curbyte <= 0x7E)
		        || (curbyte >= 0x81 && curbyte <= 0xFE)))
		{
			count_johab ++;
		}

	   } /* if (last_cjkbyte >= 0x81 && last_cjkbyte <= 0xFE) */
	     else { 
		    if (curbyte >= 0x40 && curbyte <= 0xFC && curbyte != 0x7F) {
			count_sjis ++;
		    }
	   }
	  } /* if do_auto_detect */
	}

	/* shift CJK byte state */
	last2_cjkbyte = last_cjkbyte;
	last_cjkbyte = curbyte;

	/* end character encoding auto-detection */
}

int
get_line (fd, buffer, len, do_auto_detect)
  int fd;
  register char buffer [MAX_CHARS];
  int * len;
  FLAG do_auto_detect;
{
  register char * cur_pos = current_bufpos;
  char * begin = buffer;
  char * fini = (char *) (buffer + MAX_CHARS - 2) /* leave space for '\n\0' */;
  int ret = 0;
  int Ulineend_state = 0;
  character curbyte;

#define dont_debug_overlong
#ifdef debug_overlong
  fini = (char *) (buffer + 20) /* debug overlong line input */;
#endif

  /* read one line */
  do {	/* read one byte */
	if (cur_pos == last_bufpos) {
		if (utf16_file && consider_utf16) {
		    if (next_byte >= fini_byte) {
			do {
				interrupted = False;
				if (alloc_UTF16buf () == ERRORS) {
					return ERRORS;
				}
				read_bytes = read (fd, UTF16buf, filebuflen);
				trace_read (("read utf16 (%d, %X, %d) %d\n", fd, UTF16buf, filebuflen, read_bytes));
			} while (interrupted ||
				 (read_bytes == -1 && geterrno () == EINTR));
			if (read_bytes <= 0) {
				read_chars = read_bytes;
				break;
			}
			next_byte = UTF16buf;
			fini_byte = & UTF16buf [read_bytes];
		    }
		    read_chars = UTF16_transform (filebuf, filebuflen, & next_byte, fini_byte);
		    if (count_1read_op == 0) {
			if (strncmp (filebuf, "﻿", 3) == 0) {
				/* already transformed UTF-8 BOM */
				BOM = True;
			}
			count_1read_op = 1;
		    }
		} else {
		    do {
			interrupted = False;
			read_chars = read (fd, filebuf, filebuflen);
			trace_read (("read (%d, %X, %d) %d\n", fd, filebuf, filebuflen, read_chars));
		    } while (interrupted ||
			     (read_chars == -1 && geterrno () == EINTR));
		    if (read_chars <= 0) {
			break;
		    }
		}
		last_bufpos = & filebuf [read_chars];
		cur_pos = filebuf;

		if (count_1read_op == 0 && consider_utf16) {
			if (! (utf8_text && utf16_file)
			    && strncmp (filebuf, "﻿", 3) == 0) {
				/* UTF-8 BOM */
				BOM = True;
			} else if (strncmp (filebuf, "\376\377", 2) == 0
				   && do_auto_detect) {
				/* big endian UTF-16 BOM */
				(void) set_text_encoding (":16", ' ', "BOM 16");
				BOM = True;
				/* strip converted UTF-8 BOM from text */
				cur_pos += 3;
			} else if (strncmp (filebuf, "\377\376", 2) == 0
				   && do_auto_detect) {
				/* little-endian UTF-16 BOM */
				(void) set_text_encoding (":61", ' ', "BOM 61");
				BOM = True;
				/* strip converted UTF-8 BOM from text */
				cur_pos += 3;
			} else if (utf8_text && utf16_file) { /* UTF-16 pre-selection */
			} else if (do_auto_detect) {
				/* UTF-16 auto-detection */
				char * sp = filebuf;
				int even_0 = 0;
				int odd_0 = 0;
				FLAG odd = False;
				while (sp < last_bufpos) {
					if (* sp ++ == '\0') {
						if (odd) {
							odd_0 ++;
						} else {
							even_0 ++;
						}
					}
					odd = ! odd;
				}
				if (even_0 > read_chars / 133
				    && even_0 > 2 * (odd_0 + 1)) {
					/* big endian UTF-16 */
					(void) set_text_encoding (":16", ' ', "detect 16");
				} else if (odd_0 > read_chars / 133
					   && odd_0 > 2 * (even_0 + 1)) {
					/* little-endian UTF-16 */
					(void) set_text_encoding (":61", ' ', "detect 61");
				}
			}

			if (utf16_file) {
				/* do_auto_detect = False; */

				/* move UTF-16 input to UTF-16 buffer */
				if (alloc_UTF16buf () == ERRORS) {
					return ERRORS;
				}
				memcpy (UTF16buf, filebuf, (unsigned int) read_chars);
				read_bytes = read_chars;
				next_byte = UTF16buf;
				fini_byte = & UTF16buf [read_bytes];

				/* transform to UTF-8 */
				read_chars = UTF16_transform (filebuf, filebuflen, & next_byte, fini_byte);
				last_bufpos = & filebuf [read_chars];
			}
			count_1read_op = 1;
		}
	}

	curbyte = * cur_pos;


	auto_detect_byte (curbyte, do_auto_detect);


	/* NUL character handling */
	if (* cur_pos == '\0') {
		* cur_pos = '\n';
		ret = NUL_LINE;
	}


	/* detect if no more lines available */
	if (cur_pos == last_bufpos) {
		break;
	}

	/* handle if line buffer full */
	if (buffer > fini - 6 && * cur_pos != '\n') {
	    /* try not to split within a UTF-8 sequence */
	    if (buffer == fini ||	/* last chance to split! */
		(cjk_text && ! do_auto_detect
		? charbegin (begin, buffer) == buffer
		: (* cur_pos & 0xC0) != 0x80
		))
	    {
		/*get_l_err1 = "Line(s) too long - split into virtual lines";*/
		* buffer ++ = '\n';
		ret = SPLIT_LINE;
		break;
	    }
	}


	/* copy byte from input buffer into line buffer */
	curbyte = * buffer = * cur_pos;

	/* advance buffer pointers, handle CR/CRLF lookahead */
	if (lineends_detectCR && curbyte != '\n' && buffer != begin && * (buffer - 1) == '\r') {
		curbyte = '\n';
	} else {
		buffer ++;
		cur_pos ++;
	}


	/* check multi-byte line ends */
	if (utf8_lineends && (auto_detect || utf8_text)) {
		if (Ulineend_state == 0 && curbyte == (character) '�') {
			Ulineend_state = 1;
		} else if (Ulineend_state > 0) {
			if (Ulineend_state == 1 && curbyte == (character) '�') {
				Ulineend_state = 2;
			} else if (Ulineend_state == 2 && curbyte == (character) '�') {
				Ulineend_state = 3;
				/* LS   detected */
				curbyte = '\n';
			} else if (Ulineend_state == 2 && curbyte == (character) '�') {
				Ulineend_state = 3;
				/* PS   detected */
				curbyte = '\n';
			} else {
				Ulineend_state = 0;
			}
		}
	}
  } while (curbyte != '\n');

  current_bufpos = cur_pos;

  /* handle EOF */
  if (read_chars <= 0) {
	if (buffer == begin) {
		return ERRORS;
	}
	if (* (buffer - 1) != '\n' &&
		(* (buffer - 1) != '\r' || ! lineends_detectCR)
	   ) {
		if (loading) {
			/* Add '\n' to last line of file, for internal handling */
			* buffer ++ = '\n';
		}
		ret = NO_LINE;

		/* consider incomplete UTF-8 sequence for auto-detection */
		if (count_utf_bytes > 0) {
			count_bad_utf ++;
		}
	}
  }

  * buffer = '\0';
  * len = (int) (buffer - begin);
  return ret;
}


/*
 * extract_lineend_type extracts the type of line end from the string, 
 * removes it from the string and returns it
   Handle some of the lineend transformation options:
   	+r	convert LF to CRLF
 */
lineend_type
extract_lineend_type (text, length)
  char * text;
  int length;
{
  register char * end = text + length - 1;

  if (* end == '\n') {
	end --;
	if (length > 1 && * end == '\r') {
		* end = * (end + 1);
		* (end + 1) = '\0';
		count_lineend_CRLF ++;	/* count MSDOS lines */
		if (lineends_CRLFtoLF) {
			set_modified ();
			return lineend_LF;
		}
		return lineend_CRLF;
	} else {
		count_lineend_LF ++;	/* count Unix lines */
		if (lineends_LFtoCRLF) {
			set_modified ();
			return lineend_CRLF;
		}
		return lineend_LF;
	}
  } else if (* end == '\r') {
	* end = '\n';
	count_lineend_CR ++;	/* count Mac lines */
	if (lineends_CRtoLF) {
		set_modified ();
		if (lineends_LFtoCRLF) {
			return lineend_CRLF;
		}
		return lineend_LF;
	}
	return lineend_CR;
  } else if (utf8_lineends && length >= 3 && strncmp (end - 2, " ", 3) == 0) {
	end --;
	end --;
	* end ++ = '\n';
	* end = '\0';
	return lineend_LS;	/* Unicode line separator 2028 */
  } else if (utf8_lineends && length >= 3 && strncmp (end - 2, " ", 3) == 0) {
	end --;
	end --;
	* end ++ = '\n';
	* end = '\0';
	return lineend_PS;	/* Unicode paragraph separator 2029 */
  } else {
	return lineend_NONE;
  }
}


static
void
update_file_name (newname, update)
  char * newname;
  FLAG update;
{
  char * b = basename (newname);
  copy_string (file_name, newname);	/* Save file name */
  if (hide_password_mode == 1) {
	FLAG old_hide_password = hide_password;
	hide_password = * b == '.';
	if (update && hide_password != old_hide_password) {
		RDwin ();
	}
  }
}


/*
 * Load_file loads the file with given name or the input pipe into memory.
 * If the file couldn't be opened, just an empty line is installed.
 * Buffer pointers are initialized.
 * The current position is set to the last saved position.
 *
 * Load_file_position loads and positions as follows:
	if open_linum > 0, the given position is used
	if open_linum == 0, the last saved position is used
	if open_linum < 0, no position is set (stays at beginning)
 */
static
void
load_file_position (file, with_display, to_open_linum, to_open_pos)
  char * file;
  FLAG with_display;
  int to_open_linum;
  int to_open_pos;
{
  register LINE * line = header;
  int len;
  int ret;
  long nr_of_bytes = 0L;
  long nr_of_chars = 0L;
  long nr_of_utfchars = 0L;
  long nr_of_cjkchars = 0L;
  int fd = -1;		/* Filedescriptor for file */
  FLAG locked = False;	/* lockf performed ? */
  lineend_type new_return_type;
  FLAG save_utf8_text;	/* save state of utf8_text */
  FLAG save_cjk_text;	/* save state of cjk_text */
  FLAG save_mapped_text;	/* save state of mapped_text */
  FLAG do_auto_detect = auto_detect;
  char prev_encoding_tag;
  FLAG empty = False;
  char * prev_text_encoding = get_text_encoding ();

  total_lines = 0;	/* Zero lines to start with */

  open_linum = to_open_linum;
  open_pos = to_open_pos;
  open_col = -1;

  clearscreen ();
  flush ();
  overwriteOK = False;
  writable = True;
  file_is_dir = False;

  set_quote_type (0);
  if (preselect_quote_style != NIL_PTR) {
	set_quote_style (preselect_quote_style);
  }

  if (file == NIL_PTR) {
	if (reading_pipe == False) {
		status_msg ("No file");
		empty = True;
	} else {
		fd = 0;
		file = "standard input";
	}
	file_name [0] = '\0';
  } else {
	update_file_name (file, False);
	status_line ("Accessing ", file);
	if (access (file, 0 /* F_OK */) < 0) {	/* Cannot access file */
		status_line ("New file ", file);
		overwriteOK = False;
		empty = True;
	} else if ((fd = open (file, O_RDWR | O_BINARY, 0)) >= 0) {
		overwriteOK = True;
		writable = True;
/*
		if (lockf (fd, F_LOCK, 0) < 0) {
			status_line ("No lock for ", file);
		} else {
			locked = True;
		}
*/
		if (open_linum == 0) {
			get_open_pos (file_name);
		}
	} else if ((fd = open (file, O_RDONLY | O_BINARY, 0)) >= 0) {
		overwriteOK = True;
		writable = False;
		if (open_linum == 0) {
			get_open_pos (file_name);
		}
	} else {
		error2 ("Cannot open: " /*, file */, serror ());
		empty = True;
	}
  }
  set_file_type_flags ();

  /* initiate reading file */
  loading = True;		/* Loading file, so set flag */
  reset_get_line (True);	/* must be called after get_open_pos ! */

  /* restore determined default text encoding */
	/* must be set after reset_get_line ! */
  (void) set_text_encoding (default_text_encoding, ' ', "load: def");

  if (fd >= 0) {
	if (fd > 0) {
		/* Determine file protection in case text is saved to other file */
		struct stat fstat_buf;
		if (fstat (fd, & fstat_buf) == 0) {
			fprot1 = fstat_buf.st_mode;
#ifdef S_ISDIR
			if (S_ISDIR (fstat_buf.st_mode)) {
				file_is_dir = True;
			}
#else
#ifdef S_IFDIR
			if (fstat_buf.st_mode & S_IFDIR) {
				file_is_dir = True;
			}
#endif
#endif
		} else {
			fprot1 = fprot0;
		}
	} else {
		/* Reading from a pipe, use default mode for created file */
		fprot1 = fprot0;
	}

	status_line ("Reading ", file);
	save_utf8_text = utf8_text;
	save_cjk_text = cjk_text;
	save_mapped_text = mapped_text;
	if (do_auto_detect) {
		utf8_text = False;
		cjk_text = False;
	}

#ifdef debug_timing
	long time_get = 0;
	long time_check = 0;
	long time_line = 0;
	long time_count = 0;
#endif

	mark_time (time_get);
	while (line != NIL_LINE
		&& (ret = get_line (fd, text_buffer, & len, do_auto_detect)) != ERRORS)
	{
		elapsed_mark_time (time_get, time_check);
		if (ret == NO_LINE || ret == SPLIT_LINE) {
			new_return_type = lineend_NONE;
		} else if (ret == NUL_LINE) {
			new_return_type = lineend_NUL;
		} else {
			new_return_type = extract_lineend_type (text_buffer, len);
		}
		elapsed_mark_time (time_check, time_line);
		line = line_insert (line, text_buffer, len, new_return_type);
		elapsed_time (time_line);

		/* if editing buffer cannot be filled (out of memory), 
		   assure that file is not accidentally overwritten 
		   with truncated buffer version
		 */
		if (line == NIL_LINE) {
			* file_name = '\0';
		}

		mark_time (time_count);
		nr_of_bytes += (long) len;

		if (do_auto_detect) {
			nr_of_chars += char_count (text_buffer);
			nr_of_utfchars += utf8_count (text_buffer);
			cjk_text = True;
			nr_of_cjkchars += char_count (text_buffer);
			cjk_text = False;
		} else if (utf8_text) {
			nr_of_chars += utf8_count (text_buffer);
		} else {
			nr_of_chars += char_count (text_buffer);
		}

		if (new_return_type == lineend_NONE) {
			nr_of_chars --;
			nr_of_utfchars --;
			nr_of_cjkchars --;
			nr_of_bytes --;
		}
		elapsed_mark_time (time_count, time_get);
	}
	elapsed_time (time_get);
	utf8_text = save_utf8_text;
	cjk_text = save_cjk_text;
	mapped_text = save_mapped_text;

#ifdef debug_timing
	printf ("get %ld, check %ld, line_insert %ld, count %ld\n", time_get, time_check, time_line, time_count);
#endif

	if (total_lines == 0 && line != NIL_LINE) {
		/* The file was empty */
		line = line_insert (line, "\n", 1, lineend_NONE);
	}
	clear_filebuf ();
	cur_line = header->next;
	line_number = 0;
	if (locked == False) {
		(void) close (fd);
	}

	if (count_lineend_CRLF > count_lineend_LF) {
		default_lineend = lineend_CRLF;
	}
  } else {
	/* Handle empty buffer / new file */

	/* Determine file protection in case text is saved to file */
	fprot1 = fprot0;

	/* Select DOS lineends for DOS (djgpp) version */
#ifdef msdos
	default_lineend = lineend_CRLF;
#endif

	if (lineends_LFtoCRLF) {	/* +r */
		default_lineend = lineend_CRLF;
	} else if (lineends_CRLFtoLF) {	/* -r */
		default_lineend = lineend_LF;
	}

	/* Just install an empty line */
	line = line_insert (line, "\n", 1, default_lineend);
  }


  /* auto-detection stuff */

#define dont_debug_auto_detect

#ifdef debug_auto_detect
	printf ("good CJK %d, weak CJK %d, bad CJK %d, good UTF %d, bad UTF %d\n", 
		count_good_cjk, count_weak_cjk, count_bad_cjk, count_good_utf, count_bad_utf);
	printf ("iso %d, cp1252 %d, cp850 %d, mac %d, viscii %d, tcvn %d\n", 
		count_good_iso, count_good_cp1252, count_good_cp850, count_good_mac, count_good_viscii, count_good_tcvn);
	printf ("big5 %d, gb %d, jp %d, sjis %d, uhc %d, johab %d\n", 
		count_big5, count_gb, count_jp, count_sjis, count_uhc, count_johab);
#endif

  /* consider line-end types */
  count_good_cp1252 += 5 * count_lineend_CRLF;
  count_good_cp850 += 5 * count_lineend_CRLF;
  count_good_mac += 5 * count_lineend_CR;
  count_good_iso += count_lineend_LF;
  count_sjis += 12 * count_lineend_CRLF;

  /* heuristic adjustments */
/*
	count_big5 *= 2;
	count_uhc *= 2;
	count_jp *= 3;
	count_sjis *= 1.5;
	count_good_viscii *= 0.9;
*/
	count_sjis += count_sjis1 / 2;

#ifdef debug_auto_detect
	printf ("-> iso %d, cp1252 %d, cp850 %d, mac %d, viscii %d, tcvn %d\n", 
		count_good_iso, count_good_cp1252, count_good_cp850, count_good_mac, count_good_viscii, count_good_tcvn);
	printf ("-> big5 %d, gb %d, jp %d, sjis %d, uhc %d, johab %d\n", 
		count_big5, count_gb, count_jp, count_sjis, count_uhc, count_johab);
#endif

  /* remember text encoding before auto-detection (for CJK char counting) */
  prev_encoding_tag = text_encoding_tag;

  /* filter out encodings that shall not be auto-detected */
  if (detect_encodings != NIL_PTR && * detect_encodings != '\0') {
	if (strchr (detect_encodings, 'G') == NIL_PTR) {
		count_gb = 0;
	}
	if (strchr (detect_encodings, 'B') == NIL_PTR) {
		count_big5 = 0;
	}
	if (strchr (detect_encodings, 'K') == NIL_PTR) {
		count_uhc = 0;
	}
	if (strchr (detect_encodings, 'J') == NIL_PTR) {
		count_jp = 0;
	}
	if (strchr (detect_encodings, 'S') == NIL_PTR) {
		count_sjis = 0;
		count_sjis1 = 0;
	}
	if (strchr (detect_encodings, 'C') == NIL_PTR) {
		count_cns = 0;
	}
	if (strchr (detect_encodings, 'H') == NIL_PTR) {
		count_johab = 0;
	}

	if (strpbrk (detect_encodings, "V8") == NIL_PTR) {
		count_good_viscii = 0;
	}
	if (strpbrk (detect_encodings, "N8") == NIL_PTR) {
		count_good_tcvn = 0;
	}

	if (strpbrk (detect_encodings, "L8") == NIL_PTR) {
		count_good_iso = 0;
	}
	if (strpbrk (detect_encodings, "W8") == NIL_PTR) {
		count_good_cp1252 = 0;
	}
	if (strpbrk (detect_encodings, "P8") == NIL_PTR) {
		count_good_cp850 = 0;
	}
	if (strpbrk (detect_encodings, "M8") == NIL_PTR) {
		count_good_mac = 0;
	}
  }

  /* Unicode encoding detection */
  if (! empty && do_auto_detect && ! utf16_file) {
	if (BOM || 
#ifdef utf_preference_in_utf_term
		utf8_screen ? count_good_utf >= count_bad_utf
				: count_good_utf > count_bad_utf
#else
		count_good_utf >= count_bad_utf
#endif
	   )
	{
		nr_of_chars = nr_of_utfchars;
		(void) set_text_encoding (NIL_PTR, 'U', "load: U");
	} else {
		if (strisprefix ("UTF", get_text_encoding ())) {
			(void) set_text_encoding (NIL_PTR, 'L', "load: L");
		} else {
			/* allow fallback to default encoding set on function entry */
		}

		if (count_good_cjk > 
#ifdef poor_tuning_attempt
			count_bad_cjk * 2 + count_weak_cjk * 5 / nr_of_cjkchars
#else
			count_bad_cjk * 2 + count_weak_cjk / 5
#endif
		   && count_good_cjk > count_good_iso
		   && (/* at least one CJK encoding is enabled for auto-detection */
			   count_gb > 0
			|| count_big5 > 0
			|| count_uhc > 0
			|| count_jp > 0
			|| count_sjis > 0
		      )
		   ) {
			/* setting cjk_text = True; */
			(void) set_text_encoding (":??", ' ', "load: CJK");
			nr_of_chars = nr_of_cjkchars;
		} else if (
		    /* count_good_viscii / 2 > count_good_cjk + count_weak_cjk && */
		    /* count_good_viscii / 3 > count_good_cjk + count_weak_cjk - count_bad_cjk && */
		       count_good_viscii > count_good_iso
		    && count_good_viscii > count_good_cp1252
		    && count_good_viscii > count_good_cp850
		    && count_good_viscii > count_good_mac
		   ) {
			(void) set_text_encoding ("VISCII", 'V', "detect");
		} else if (count_good_cp1252 > count_good_iso && 
			count_good_cp1252 > count_good_cp850 && 
			count_good_cp1252 > count_good_mac) {
			(void) set_text_encoding ("CP1252", 'W', "detect");
		} else if (count_good_cp850 > count_good_iso && 
			count_good_cp850 > count_good_mac) {
			(void) set_text_encoding ("CP850", 'P', "detect");
		} else if (count_good_mac > count_good_iso) {
			(void) set_text_encoding ("MacRoman", 'M', "detect");
		} else {
			/* leave default/fallback encoding */
		}
	}
  }

  /* detect CJK encoding based on 
     count_big5 count_gb count_uhc count_jp
  */
  if (! empty && cjk_text && do_auto_detect && ! (utf8_text && utf16_file)) {
	if (count_gb > count_big5 && count_gb > count_uhc && count_gb > count_jp && count_gb > count_sjis) {
		(void) set_text_encoding ("GB", 'G', "detect CJK");
	} else if (count_uhc > count_big5 && count_uhc > count_jp && count_uhc > count_sjis) {
		(void) set_text_encoding ("UHC", 'K', "detect CJK");
	} else if (count_big5 > count_uhc && count_big5 > count_jp && count_big5 > count_sjis) {
		(void) set_text_encoding ("Big5", 'B', "detect CJK");
	} else if (count_jp > count_sjis) {
		(void) set_text_encoding ("EUC-JP", 'J', "detect CJK");
	} else if (count_sjis > 0) {
		(void) set_text_encoding ("Shift-JIS", 'S', "detect CJK");
	}
  }

  /* detect style of quotation marks */
  if (quote_type == 0) {
	determine_quote_style ();
  }

  /* if text encoding changed, reset previous one, then toggle */
  if (! streq (prev_text_encoding, get_text_encoding ())) {
	char * new_text_encoding = get_text_encoding ();
	(void) set_text_encoding (prev_text_encoding, ' ', "load: prev");
	change_encoding (new_text_encoding);
	/* -> ... set_text_encoding (new_text_encoding, ' ', "men: change_encoding"); */
  }

  /* end auto-detection stuff */


  if (fd >= 0) {
	if (line != NIL_LINE) {
	   show_get_l_errors ();
	   fstatus ("Read", nr_of_bytes, nr_of_chars);
	}
  }

  if (line == NIL_LINE) {
	sleep (2) /* give time to read allocation error msg */;
	viewonly = True;
	modified = False;
  }

  reset (header->next, 0);	/* Initialize pointers */
  move_to (0, 0);
  loading = False;		/* Stop loading, reset flag */

  if (open_linum > 0) {
	LINE * open_line = proceed (header->next, open_linum - 1);
	char * cpoi;
	int cur_column = 0;

	move_to (0, find_y_w_o_RD (open_line));

	/* re-position within line */
	cpoi = cur_line->text;
	if (open_col >= 0) {
		char * prev_cpoi = cpoi;
		while (* cpoi != '\n' && cur_column <= open_col) {
			prev_cpoi = cpoi;
			advance_char_scr (& cpoi, & cur_column, cur_line->text);
		}
		if (cur_column <= open_col) {
			prev_cpoi = cpoi;
		}
		move_address (prev_cpoi, y);
	} else {
		while (* cpoi != '\n' && open_pos > 0) {
			advance_char (& cpoi);
			open_pos --;
		}
		move_address (cpoi, y);
	}

	if (with_display) {
		display (0, top_line, last_y, 0);
	}
	if (! cjk_text || prev_encoding_tag == text_encoding_tag) {
		fstatus ("Read", nr_of_bytes, nr_of_chars);
	} else {
		fstatus ("Read", nr_of_bytes, -1L);
	}
	Markn (0);
  } else if (with_display) {
	display (0, top_line, last_y, 0);
	move_to (0, 0);
  }

#ifdef unix
  RD_window_title ();
#endif
}

void
load_file (file, with_display)
  char * file;
  FLAG with_display;
{
  load_file_position (file, with_display, 0, 0);
}


#ifdef msdos
# ifdef __TURBOC__
#include "turbodir.h"
# else
#include <dir.h>
# endif
struct ffblk ffblock;
FLAG ffblockvalid = False;
char wildfile [maxLINE_LEN];
char drive [9], dir [maxLINE_LEN], name [13], ext [5];
#endif

void
load_wild_file (file, with_display)
  char * file;
  FLAG with_display;
{
#ifdef msdos
  char dumdrive [9];
  char dumdir [maxLINE_LEN];
#endif

#ifdef msdos
  ffblockvalid = False;
#endif
  if (file == NIL_PTR) {
	load_file (file, with_display);
  } else {

#ifdef msdos
/*	(void) fnsplit (fnamv [fnami], drive, dir, name, ext);
	if (findfirst (fnamv [fnami], & ffblock, 0) == 0) {
*/
	(void) fnsplit (file, drive, dir, name, ext);
	if (findfirst (file, & ffblock, 0) == 0) {
		ffblockvalid = True;
		(void) fnsplit (ffblock.ff_name, dumdrive, dumdir, name, ext);
		fnmerge (wildfile, drive, dir, name, ext);
		load_file (wildfile, with_display);
	} else
#endif
	{
		load_file (file, with_display);
	}
  }
}


/*
 * Ask user if named file should be overwritten.
 */
FLAG
checkoverwrite (name)
  char * name;
{
  character c;

  status_line ("Checking ", name);
  if (access (name, 0 /* F_OK */) < 0) {	/* Cannot access file */
	return True;	/* thus no danger of unwanted damage */
  }

  status_line (name [0] ? name : empty_buffer_name,
			": OK to overwrite? (y/n)");
  c = promptyn ();
  clear_status ();
  if (c == 'y') {
	return True;
  } else if (c == 'n') {
	return False;
  } else {
/*	quit = False;	abort character has been given */
	return False;
  }
}

/*
 * Attach new file name to buffer
 */
void
NN ()
{
  char file [maxLINE_LEN];	/* Buffer for new file name */

  if (restricted) {
	restrictederr ();
	return;
  }
  if (get_filename ("Enter new file name:", file) == ERRORS) {
	return;
  }

  overwriteOK = False;
  writable = True;
  update_file_name (file, True);
#ifdef unix
  if (modified) {
	RD_window_title ();
  }
#endif
  set_modified ();	/* cf. CHDI command */
  clear_status ();
}


/*======================================================================*\
|*			File I/O basics					*|
\*======================================================================*/

/*
 * Flush the I/O buffer on filedescriptor fd.
 */
int
flush_filebuf (fd)
  int fd;
{
  if (filebuf_count <= 0) {	/* There is nothing to flush */
	return FINE;
  } else {
	char * writepoi = filebuf;
	int written = 0;
	int none_count = 0;
/*	int less_count = 0;	*/

	while (filebuf_count > 0) {
		written = write (fd, writepoi, filebuf_count);
		if (written == -1) {
			if (geterrno () == EINTR && winchg) {
				/* try again */
			} else {
				bad_write (fd);
				return ERRORS;
			}
		} else if (written == 0) {
			none_count ++;
			if (none_count > 20) {
				bad_write (fd);
				return ERRORS;
			}
		} else {
			filebuf_count -= written;
			writepoi += written;
		}
	}
  }
  filebuf_count = 0;
  return FINE;
}

/*
 * writechar does a buffered output to file.
 */
int
writechar (fd, c)
  int fd;
  char c;
{
  filebuf [filebuf_count ++] = c;
  if (filebuf_count == filebuflen) {
	return flush_filebuf (fd);
  }
  return FINE;
}

/*
 * writeucs writes a UCS Unicode code in UTF-16
 * Return # words written or ERRORS.
 */
static
int
writeucs (fd, c)
  int fd;
  unsigned long c;
{
  int err = FINE;

  if (c > (unsigned long) 0x10FFFF) {
	return writeucs (fd, 0xFFFD);
  } else if (c > (unsigned long) 0xFFFF) {
	err = 2;
	c -= 0x10000;
	err |= writeucs (fd, 0xD800 | (c >> 10));
	err |= writeucs (fd, 0xDC00 | (c & 0x03FF));
  } else {
	err = 1;
	if (utf16_little_endian) {
		err |= writechar (fd, c & 0xFF);
		err |= writechar (fd, c >> 8);
	} else {
		err |= writechar (fd, c >> 8);
		err |= writechar (fd, c & 0xFF);
	}
  }

  return err;
}

/*
 * writelechar writes a line-end character to file
   Only called by write_lineend.
 */
static
int
writelechar (fd, c, handle_utf16)
  int fd;
  char c;
  FLAG handle_utf16;
{
  if (utf8_text && utf16_file && handle_utf16) {
	return writeucs (fd, (character) c);
  } else {
	return writechar (fd, c);
  }
}

/*
 * write_lineend writes a line-end in the respective form to file
   Called by write_line, yank_text.
 */
int
write_lineend (fd, return_type, handle_utf16)
  register int fd;
  lineend_type return_type;
  FLAG handle_utf16;
{
  switch (return_type) {
    case lineend_NONE:	return 0;
    case lineend_NUL:	if (writelechar (fd, '\0', handle_utf16) == ERRORS) {
				return ERRORS;
			}
			return 1;
    case lineend_LF:	if (writelechar (fd, '\n', handle_utf16) == ERRORS) {
				return ERRORS;
			}
			return 1;
    case lineend_CRLF:	if (writelechar (fd, '\r', handle_utf16) == ERRORS) {
				return ERRORS;
			}
			if (writelechar (fd, '\n', handle_utf16) == ERRORS) {
				return ERRORS;
			}
			return 2;
    case lineend_CR:	if (writelechar (fd, '\r', handle_utf16) == ERRORS) {
				return ERRORS;
			}
			return 1;
    case lineend_LS:	/* Unicode line separator 2028:   */
			if (utf8_text && utf16_file && handle_utf16) {
				if (writeucs (fd, 0x2028) == ERRORS) {
					return ERRORS;
				}
				return 1;
			} else {
				if (writelechar (fd, '\342', handle_utf16) == ERRORS) {
					return ERRORS;
				}
				if (writelechar (fd, '\200', handle_utf16) == ERRORS) {
					return ERRORS;
				}
				if (writelechar (fd, '\250', handle_utf16) == ERRORS) {
					return ERRORS;
				}
				return 3;
			}
    case lineend_PS:	/* Unicode paragraph separator 2029:   */
			if (utf8_text && utf16_file && handle_utf16) {
				if (writeucs (fd, 0x2029) == ERRORS) {
					return ERRORS;
				}
				return 1;
			} else {
				if (writelechar (fd, '\342', handle_utf16) == ERRORS) {
					return ERRORS;
				}
				if (writelechar (fd, '\200', handle_utf16) == ERRORS) {
					return ERRORS;
				}
				if (writelechar (fd, '\251', handle_utf16) == ERRORS) {
					return ERRORS;
				}
				return 3;
			}
    default:		if (writelechar (fd, '\n', handle_utf16) == ERRORS) {
				return ERRORS;
			}
			return 1;
  }
}

/*
 * Writestring writes the given string on the given filedescriptor.
 * (buffered via writechar via misused screen buffer!)
 * Return # bytes written or ERRORS.
   Only called by write_file, so handle_utf16 is always True.
 */
static
int
write_line (fd, text, return_type, handle_utf16)
  int fd;
  char * text;
  lineend_type return_type;
  FLAG handle_utf16;
{
  int len;
  int ccount = 0;

  while (* text != '\0') {
	if (* text == '\n') {
		/* handle different line ends */
		len = write_lineend (fd, return_type, handle_utf16);
		if (len == ERRORS) {
			return ERRORS;
		}
		text ++;
		ccount += len;
	} else {
		if (utf8_text && utf16_file && handle_utf16) {
			unsigned long unichar;
			int utflen;
			utf8_info (text, & utflen, & unichar);
			if (UTF8_len (* text) == utflen) {
				len = writeucs (fd, unichar);
			} else {
				len = writeucs (fd, 0xFFFD);
			}
			if (len == ERRORS) {
				return ERRORS;
			}
			advance_utf8 (& text);
			ccount += len;
		} else {
			if (writechar (fd, * text) == ERRORS) {
				return ERRORS;
			}
			text ++;
			ccount ++;
		}
	}
  }

  if (utf8_text && utf16_file && handle_utf16) {
	return ccount * 2;
  } else {
	return ccount;
  }
}


/* Call graph for writing functions:
	panic --\------> panicwrite --------------------------\
		 > QUED ----------\			       \
	ESC q --/		   > ask_save --\		> write_file
	ESC e ---> EDIT --> edit_file -/	 \	       /
	ESC v ---> VIEW -/			  \	      /
	ESC w -------------------> WT -------------> write_text
	ESC W -------------------> WTU -----------/
	ESC z -------------------> SUSP ---------/
	ESC ESC -> EXED ---------> EXFILE ------/
			\--------> EXMINED ----/
	ESC t -------------------> Stag ------/
*/
long write_count;	/* number of bytes written */
long chars_written;	/* number of chars written */

/*
 * Write text in memory to file.
 */
static
void
write_file (fd)
  int fd;
{
  register LINE * line;
  int ret = FINE;
  static FLAG handle_utf16 = True;

  write_count = 0L;
  chars_written = 0L;
  clear_filebuf ();

  if (utf8_text && utf16_file && handle_utf16) {
	/* prepend BOM if there was one */
	if (BOM && strncmp (header->next->text, "﻿", 3) != 0) {
		ret = write_line (fd, "﻿", lineend_NONE, handle_utf16);
		if (ret == ERRORS) {
			error2 ("Write failed (File incomplete): ", serror ());
			write_count = -1L;
			chars_written = -1L;
		} else {
			ret = FINE;
			write_count = 2;
			chars_written = 1;
		}
	}
  }

  if (ret == FINE) {
    for (line = header->next; line != tail; line = line->next) {
	ret = write_line (fd, line->text, line->return_type, handle_utf16);
	if (ret == ERRORS) {
		error2 ("Write failed (File incomplete): ", serror ());
		write_count = -1L;
		chars_written = -1L;
		break;
	}
	write_count += (long) ret;
	chars_written += (long) char_count (line->text);
	if (line->return_type == lineend_NONE) {
		chars_written --;
	}
    }
  }

  if (write_count > 0L && flush_filebuf (fd) == ERRORS) {
	if (ret != ERRORS) {
		error2 ("Write failed (File incomplete): ", serror ());
		ret = ERRORS;
	}
	write_count = -1L;
	chars_written = -1L;
  }

  if (close (fd) == -1) {
	if (ret != ERRORS) {
		error2 ("Write failed (File incomplete): ", serror ());
	}
	write_count = -1L;
	chars_written = -1L;
  }
}

int
panicwrite ()
{
  int fd;
/*  fd = creat (panic_file, bufprot);	*/
  fd = open (panic_file, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, bufprot);
  write_file (fd);
  if (write_count == -1L) {
	return ERRORS;
  } else {
	return FINE;
  }
}

/**
   Write text to its associated file.
 */
int
write_text (forceflag)
  FLAG forceflag;
{
  char file [maxLINE_LEN];	/* Buffer for new file name */
  int fd;			/* Filedescriptor of file */
  int ret;

  if (writing_pipe) {
    fd = STD_OUT;
    status_line ("Writing ", "to standard output");
    writing_pipe = False; /* no further write to same stream possible */
  } else {
    if (forceflag == False && modified == False) {
	if (file_name [0] != '\0') {
		fstatus ("(Write not necessary)", -1L, -1L);
		(void) save_open_pos (file_name, hop_flag);
	} else {
		status_msg ("Write not necessary.");
	}
	return FINE;
    }

    /* Check if file_name is valid and if file can be written */
    if (file_name [0] == '\0' || writable == False) {
	overwriteOK = False;
	if ((ret = get_filename ("Saving edited text - enter file name:", file))
		!= FINE)
	{
		return ret;
	}
	update_file_name (file, True);
#ifdef unix
	RD_window_title ();
#endif
    }
    if (overwriteOK == False) {
	if (checkoverwrite (file_name)) {
		overwriteOK = True;
	} else {
		if (quit == False) {
			writable = False;
		}
		return ERRORS;
	}
    }
    status_line ("Opening ", file_name);
/*    fd = creat (file_name, fprot1 | ((fprot1 >> 2) & xprot));	*/
    fd = open (file_name, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, fprot1 | ((fprot1 >> 2) & xprot));
    if (fd < 0) {	/* Empty file */
	error2 ("Cannot create or write: " /*, file_name */, serror ());
	writable = False;
	return ERRORS;
    } else {
	writable = True;
    }

    status_line ("Writing ", file_name);
  }

  write_file (fd);

  if (write_count == -1L) {
	return ERRORS;
  }

  modified = False;
#ifdef unix
  RD_window_title ();
#endif
  reading_pipe = False;	/* File name is now assigned */

/* Display how many chars (and lines) were written */
  fstatus ("Wrote", write_count, chars_written);
/*  fstatus ("Wrote", -1L); */
  (void) save_open_pos (file_name, hop_flag);
  return FINE;
}


/*======================================================================*\
|*			File commands					*|
\*======================================================================*/

FLAG
save_text_load_file (fn)
  char * fn;
{
	if (modified) {
		if (write_text (False) == ERRORS) {
			return ERRORS;
		}
	}

	/* Free old linked list, initialize global variables and load new file */
	initialize ();
	clearscreen ();
	load_file (fn, True);
	return FINE;
}

void
SAVEAS ()
{
  if (restricted) {
	restrictederr ();
	return;
  }
  NN ();
  WT ();
}

void
WT ()
{
  (void) write_text (False);
}

void
WTU ()
{
  if (restricted && viewonly) {
	restrictederr ();
	return;
  }
  (void) write_text (True);
}

/*
 * Save current file pos
 */
void
SAVPOS ()
{
  if (file_name [0] != '\0') {
	fstatus ("Remembering file position", -1L, -1L);
	if (save_open_pos (file_name, 1) == False) {
		error2 ("Error when saving file position to ", mark_file);
	}
  }
}

/*
 * Ask the user if he wants to save the file or not.
 */
static
int
ask_save ()
{
  register character c;

  status_line (file_name [0] ? file_name : empty_buffer_name,
			" has been modified. Save? (y/n)");
  c = promptyn ();
  clear_status ();
  if (c == 'y') {
	return write_text (False);
  } else if (c == 'n') {
	return FINE;
  } else {
	quit = False;	/* abort character has been given */
	return ERRORS;
  }
}

/*
 * Edit/view another file. If the current file has been modified, 
 * ask whether the user wants to save it.
 * (We could allow to switch between edit and view mode without changing 
 * the file, but we would have to consider carefully the relationship 
 * between viewonly and modified.)
 */
static
void
edit_file (prompt, vomode)
  char * prompt;
  FLAG vomode;
{
  char new_file [maxLINE_LEN];	/* Buffer to hold new file name */

  if (modified && viewonly == False && ask_save () != FINE) {
	return;
  }

/* Get new file name */
  if (get_filename (prompt, new_file) == ERRORS) {
	return;
  }

  viewonly = vomode;

/* Free old linked list, initialize global variables and load new file */
  initialize ();
  clearscreen ();
  load_wild_file (new_file [0] == '\0' ? NIL_PTR : new_file, True);
}

#ifdef not_used
static
void
edit_this_file (new_file, vomode)
  char * new_file;
  FLAG vomode;
{
  if (modified && viewonly == False && ask_save () != FINE) {
	return;
  }

  viewonly = vomode;

/* Free old linked list, initialize global variables and load new file */
  initialize ();
  clearscreen ();
  load_wild_file (new_file [0] == '\0' ? NIL_PTR : new_file, True);
}
#endif

void
EDIT ()
{
  if (restricted) {
	restrictederr ();
	return;
  }
  edit_file ("Edit file:", False);
}

void
VIEW ()
{
  if (restricted) {
	restrictederr ();
	return;
  }
  edit_file ("View file:", True);
}

void
EDITmode ()
{
  if (restricted) {
	restrictederr ();
	return;
  }
  viewonly = False;
  FSTAT ();
  flags_changed = True;
}

void
VIEWmode ()
{
  if (modified == False) {
	viewonly = True;
	FSTAT ();
	flags_changed = True;
  } else {
	error ("Cannot view only - already modified");
  }
}

void
toggle_VIEWmode ()
{
  if (viewonly) {
	EDITmode ();
  } else {
	VIEWmode ();
  }
}

void
view_help (helpfile, item)
  char * helpfile;
  char * item;
{
  char searchstring [maxLINE_LEN];

  /* unless already viewing help, save edited text */
  if (viewing_help == False) {
	if (modified) {
		if (write_text (False) != FINE) {
			return;
		}
	}

	/* save current position */
	save_cur_line = line_number;
	save_cur_pos = get_cur_pos ();

	/* save editing mode and file name */
	save_viewonly = viewonly;
	save_restricted = restricted;
	copy_string (save_file_name, file_name);

	/* set mode appropriate for viewing online help */
	viewonly = True;
	restricted = True;
	viewing_help = True;

	/* load online help file */
	initialize ();
	clearscreen ();
	load_file_position (helpfile, True, -1, 0);
  }

  /* position to selected help topic */
  BFILE ();
  build_string (searchstring, "mined help topic '%s'", item);
  search_for (searchstring, FORWARD, True);
}

static
void
end_view_help ()
{
  viewonly = save_viewonly;
  restricted = save_restricted;
  viewing_help = False;

  initialize ();
  clearscreen ();
  load_file_position (save_file_name, True, save_cur_line, save_cur_pos);
}

static
void
quit_mined ()
{
  if (viewing_help) {
	end_view_help ();
	return;
  }

  delete_yank_files ();

  clear_status ();
  set_cursor (0, YMAX);
  putchar ('\n');
#ifdef unix
  clear_window_title ();
#endif
  flush ();
  raw_mode (False);
  exit (0);
}

void
edit_nth_file (n)
  int n;
{
  int number;
  int index;

  if (modified && viewonly == False && ask_save () != FINE) {
	return;
  }

  if (n == -1) {
	status_msg ("Edit which file (enter number) or # to reload current file");
	index = readcharacter ();
	if (quit) {
		return;
	}
	if (index == '#') {
		n = fnami;
	} else {
		index = get_number ("Edit which file (enter number) ...", index, & number);
		if (index == ERRORS) {
			return;
		}
		n = number - 1 + fnami_min;
	}
  }
  if (n < fnami_min) {
	n = fnami_min;
  }
  if (n > fnami_max) {
	n = fnami_max;
  }

  /* Now proceed to load nth file */
  viewonly = init_viewonly;
  /* Free old linked list, initialize global variables and load new file */
  initialize ();
  clearscreen ();

  fnami = n;
  if (fnami < fnami_min) {
	load_wild_file (NIL_PTR, True);
  } else {
	load_wild_file (fnamv [fnami], True);
  }
}

void
nextfile (exitiflast)
	FLAG exitiflast;
{
#ifdef msdos
  char dumdrive [9];
  char dumdir [maxLINE_LEN];
#endif

  if (hop_flag > 0) {
	edit_nth_file (fnami_max);
  } else {
#ifdef msdos
	if (modified && viewonly == False && ask_save () != FINE) {
		return;
	}

	if (ffblockvalid && findnext (& ffblock) == 0) {
		/* Discard edit buffer, initialize and load new file */
		initialize ();
		clearscreen ();
		(void) fnsplit (ffblock.ff_name, dumdrive, dumdir, name, ext);
		fnmerge (wildfile, drive, dir, name, ext);
		load_file (wildfile, True);
		return;
	}
	ffblockvalid = False;
#endif
	if (fnami >= fnami_max || fnami == 0) {
		if (exitiflast) {
			quit_mined ();
		} else {
			error ("Already at last file");
		}
	} else {
		edit_nth_file (fnami + 1);
	}
  }
}

void
NXTFILE ()
{
  Pushmark ();

  nextfile (False);
}

void
PRVFILE ()
{
  Pushmark ();

  if (hop_flag > 0) {
	edit_nth_file (fnami_min);
  } else if (fnami <= fnami_min) {
#ifdef msdos
		error ("Already at first file name");
#else
		error ("Already at first file");
#endif
		return;
  } else {
	edit_nth_file (fnami - 1);
  }
}

void
NTHFILE ()
{
  edit_nth_file (-1);
}


/*======================================================================*\
|*			Tag search with file change			*|
\*======================================================================*/

static
int
get_tagline (idf, filename, search)
  char * idf;
  char * filename;
  char * search;
{
  int tags_fd = open ("tags", O_RDONLY | O_BINARY, 0);
  FLAG modif = modified;
  unsigned int len = strlen (idf);
  int dumlen;
  char * poi;
  char * outpoi;
  char lastpat = '\0';
  FLAG found;

  flush ();	/* clear the shared screen/get_line buffer! */

  reset_get_line (False);
  if (tags_fd >= 0) {
	found = False;
	while (found == False && (get_line (tags_fd, text_buffer, & dumlen, False)) != ERRORS) {
	    if (strncmp (idf, text_buffer, len) == 0 && text_buffer [len] == '\t') {
		found = True;
		poi = text_buffer + len + 1;

		outpoi = filename;
		while (* poi != '\0' && * poi != '\t') {
			* outpoi ++ = * poi ++;
		}
		* outpoi = '\0';

		outpoi = search;
		poi ++;
		if (* poi == '/') {
			poi ++;
		}
		while (* poi != '\0' && (* poi != '/' || lastpat == '\\')) {
			if (* poi == '[' || * poi == ']' || * poi == '*') {
				* outpoi ++ = '\\';
			}
			lastpat = * poi ++;
			* outpoi ++ = lastpat;
		}
		* outpoi = '\0';
	    }
	}
	(void) close (tags_fd);
	clear_filebuf ();
	clear_get_line ();
	modified = modif; /* don't let the tags file affect the modified flag */
	if (found == False) {
		error2 ("Identifier not found in tags file: ", idf);
		return ERRORS;
	} else {
		return FINE;
	}
  } else {
	error ("No tags file present; apply the ctags command to your source files");
	return ERRORS;
  }
}

/*
 * Stag () opens file and moves to idf, using tags file
 */
void
Stag ()
{
  char idf_buf [MAX_CHARS];	/* identifier to search for */
  char new_file [maxLINE_LEN];	/* Buffer to hold new file name */
  char search [MAX_CHARS];	/* search expression */
  int lineno;

  if (hop_flag > 0) {
	if (get_string ("Enter identifier (to locate definition):", idf_buf, True, "") != FINE) {
		return;
	}
  } else {
	if (get_idf (idf_buf, cur_text, cur_line->text) == ERRORS) {
		return;
	}
  }

  if (get_tagline (idf_buf, new_file, search) == ERRORS) {
	return;
  }

  Pushmark ();

  if (! streq (new_file, file_name)) {
	if (save_text_load_file (new_file) == ERRORS) {
		return;
	}
  }

  if (* search >= '0' && * search <= '9') {
	(void) scan_int (search, & lineno);
	goline (lineno);
  } else {
	search_for (search, FORWARD, False);
  }
}


/*======================================================================*\
|*			Checkin/out					*|
\*======================================================================*/

/*
 * Checkout (from version managing system).
 */
void
checkout ()
{
  int save_cur_pos;
  int save_cur_line;
  char syscommand [maxLINE_LEN];	/* Buffer for full system command */
  int sysres;

	if (modified) {
		if (write_text (False) != FINE) {
			return;
		}
	}

	/* save current position */
	save_cur_line = line_number;
	save_cur_pos = get_cur_pos ();

	/* try to check out */
	raw_mode (False);
	build_string (syscommand, "co %s", file_name);
	sysres = system (syscommand);
	sleep (1);
	raw_mode (True);
	RDwin ();
	if (sysres != 0) {
		error ("Checkout failed");
	}

	/* reload file */
	initialize ();
	clearscreen ();
	load_file_position (file_name, True, save_cur_line, save_cur_pos);
}

/*
 * Checkin (to version managing system).
 */
void
checkin ()
{
  char syscommand [maxLINE_LEN];	/* Buffer for full system command */
  int sysres;

	if (modified) {
		if (write_text (False) != FINE) {
			return;
		}
	}

	/* try to check in */
	raw_mode (False);
	build_string (syscommand, "ci %s", file_name);
	sysres = system (syscommand);
	sleep (1);
	raw_mode (True);
	RDwin ();
	if (sysres != 0) {
		error ("Checkin failed");
	}
}


/*======================================================================*\
|*			Exiting						*|
\*======================================================================*/

/*
 * Leave editor. If the file has changed, ask if the user wants to save it.
 */
void
QUED ()
{
  if (modified && viewonly == False && ask_save () != FINE) {
	return;
  }

  quit_mined ();
}

/*
 * Exit editing current file. If the file has changed, save it.
 * Edit next file if there is one.
 */
void
EXFILE ()
{
  if (modified) {
	if (write_text (False) != FINE) {
		return;
	}
  }

  if (hop_flag == 0) {
	nextfile (True);
  } else {
	quit_mined ();
  }
}

/*
 * Exit editor. If the file has changed, save it.
 */
void
EXMINED ()
{
  if (modified) {
	if (write_text (False) != FINE) {
		return;
	}
  }

  quit_mined ();
}

/*
 * Exit editing current file. Exit editor if multiexit flag set.
 */
void
EXED ()
{
  if (multiexit) {
	EXFILE ();
  } else {
	EXMINED ();
  }
}


/*======================================================================*\
|*			End						*|
\*======================================================================*/
