tmuck2.4/
tmuck2.4/admin/scripts/
tmuck2.4/docs/
tmuck2.4/minimal-db/
tmuck2.4/minimal-db/data/
tmuck2.4/minimal-db/logs/
tmuck2.4/minimal-db/muf/
tmuck2.4/old/
tmuck2.4/src/
tmuck2.4/src/compile/
tmuck2.4/src/editor/
tmuck2.4/src/game/
tmuck2.4/src/interface/
tmuck2.4/src/scripts/
tmuck2.4/src/utilprogs/
/* Copyright (c) 1992 by David Moore.  All rights reserved. */
/* text.c,v 2.7 1996/01/26 01:01:39 dmoore Exp */
#include "config.h"

#include <string.h>
#include <ctype.h>
#include <stdio.h>

#include "db.h"
#include "buffer.h"
#include "text.h"
#include "externs.h"

struct line {
    int length;			/* Length of line. */
    const char *data;		/* Body of line. */
    struct line *next;
    struct line *prev;
};

struct text {
    struct line *first;		/* First line of text. */
    struct line *last;		/* Quicker tail insert. */
    struct line *curr;		/* Compiler and editor for quick insert. */
    int total_lines;		/* Quicker tail insert. */
    int curr_line;		/* Compiler and editor for quick insert. */
    int nbytes;			/* Total number of bytes contained. */
};


static void free_line(struct line *line)
{
    if (line->data)
	FREE_CNT_STRING(line->data, line->length);
    FREE(line);
}


void free_text(struct text *text)
{
    struct line *temp;
    struct line *next;
    
    if (!text) return;

    for (temp = text->first; temp; temp = next) {
	next = temp->next;
	free_line(temp);
    }
   
    FREE(text);
}


void write_text(const char *fname, struct text *text)
{
    const struct line *temp;
    FILE *f;
    
    f = fopen(fname, "w");
    if (!f) {
	log_status("Couldn't open file (%s) for write_text.", fname);
	return;
    }
    
    temp = text ? text->first : NULL;
    for ( ; temp; temp = temp->next) {
	if (temp->data) {
	    fputs(temp->data, f);
	}
	fputc('\n', f);
    }
    fclose(f);
}


struct text *make_new_text(void)
{
    struct text *text;

    MALLOC(text, struct text, 1);
    text->first = NULL;
    text->last = NULL;
    text->curr = NULL;
    text->total_lines = 0;
    text->curr_line = 0;
    text->nbytes = 0;
    
    return text;
}


static struct line *make_new_line(const char *data, const int len)
{
    struct line *new;
    
    MALLOC(new, struct line, 1);
    new->length = len;
    new->data = ALLOC_CNT_STRING(data, len);
    new->next = NULL;
    new->prev = NULL;
    return new;
}


static struct line *make2_new_line(const char *data1, const int len1, const char *data2, const int len2)
{
    struct line *new;
    
    MALLOC(new, struct line, 1);
    new->length = len1 + len2;
    new->data = ALLOC_CNT2_STRING(data1, len1, data2, len2);
    new->next = NULL;
    new->prev = NULL;
    return new;
}


struct text *read_text(const char *fname)
{
    struct text *text;
    struct line *new;
    FILE *fp;
    static Buffer buf;

    text = make_new_text();

    if (fname && *fname) {
	/* If there is a file name, try to read in the associated file. */
	fp = fopen(fname, "r");
	if (fp) {
	    while (Bufgets(&buf, fp, NULL)) {
		text->total_lines++;
		new = make_new_line(Buftext(&buf), Buflen(&buf));
		text->nbytes += new->length;
		if (!text->first) {
		    text->first = text->curr = text->last = new;
		    text->curr_line = 1;
		} else {
		    text->last->next = new;
		    new->prev = text->last;
		    text->last = new;

		    /* Nice idea, every other line move curr up a spot. */
		    /* This will leave it in the middle when we are done. */
		    if (text->total_lines % 2) {
			text->curr = text->curr->next;
			text->curr_line++;
		    }
		}
	    }
	    fclose(fp);
	}
    }

    return text;
}


/* Text lines are numbered like so:
   1 - All little chupchup's must learn to
   2 - not go out and play in Storm.
   3 - Whee.
   Ranging from 1 to N. */
/* Additionally, negative entries range backwards from the end:
   -3 - All little chupchup's must learn to
   -2 - not go out and play in Storm.
   -1 - Tomato. */
static int translate_line(struct text *text, int where)
{

    if (!text) return 0;

    if (where < 0)
	where = text->total_lines + where + 1;

    return where;
}


/* This routine has a side effect of updating the current line pointer
   for the text.  If the text has no lines, NULL is returned. */
static struct line *find_line(struct text *text, int where)
{
    register int dist;
    register struct line *result;

    if (!text) return NULL;

    if (!text->curr) {
	/* Current not initialized yet. */
	text->curr = text->first;
	text->curr_line = 1;
    }

    if (where < 1 || where > text->total_lines)
	return NULL;

    if (where == text->curr_line + 1) {
	/* Immediate next line.  Most common case for editting and
	   compiling. */
	result = text->curr->next;
	where = text->curr_line + 1;
    } else if (where == 1) {
	/* First line. */
	result = text->first;
	where = 1;
    } else if (where == text->total_lines) {
	/* Last line. */
	result = text->last;
	where = text->total_lines;
    } else if (where == text->curr_line) {
	/* Current line. */
	result = text->curr;
    } else {
	if (where < text->curr_line) {
	    /* Between 1st and current line. */
	    if ((where - 1) <= (text->curr_line - where)) {
		/* Closer to 1st line. */
		result = text->first;
		for (dist = (where - 1); dist; dist--)
		    result = result->next;
	    } else {
		/* Closer to current line. */
		result = text->curr;
		for (dist = (text->curr_line - where); dist; dist--)
		    result = result->prev;
	    }
	} else {
	    /* Between current and last line. */
	    if ((where - text->curr_line) <= (text->total_lines - where)) {
		/* Closer to current line. */
		result = text->curr;
		for (dist = (where - text->curr_line); dist; dist--)
		    result = result->next;
	    } else {
		/* Closer to last line. */
		result = text->last;
		for (dist = (text->total_lines - where); dist; dist--)
		    result = result->prev;
	    }
	}
    }

    text->curr = result;
    text->curr_line = where;

    return result;
}


static int insert_line_internal(struct text *text, struct line *line, int where)
{
    struct line *pos;

    if (!text || !line) return 0;

    where = translate_line(text, where); /* Check for -N notation. */

    if (!text->first) {
	/* First line in text. */
	text->first = text->last = line;
	where = 1;
    } else if (where == 0) {
	/* Prepend to the front. */
	line->next = text->first;
	text->first->prev = line;
	text->first = line;
	where = 1;
    } else {
	pos = find_line(text, where);
	if (!pos) return 0;	/* Bad line to insert at. */

	/* Insert after this line, and check if real end. */
	line->next = pos->next;
	line->prev = pos;
	pos->next = line;
	if (line->next)
	    line->next->prev = line;
	else text->last = line;

	where = text->curr_line + 1;
    }

    /* Increment the # of lines, and make the current line be this one. */
    text->nbytes += line->length;
    text->total_lines++;
    text->curr_line = where;
    text->curr = line;

    return 1;			/* All ok. */
}


void insert_text(struct text *text, const char *data, const int len, int where)
{
    struct line *line;

    if (!text) return;

    line = make_new_line(data, len);

    if (!insert_line_internal(text, line, where)) {
	/* Unable to insert, must have been bad line number. */
	free_line(line);
    }
}


void insert2_text(struct text *text, const char *data1, const int len1, const char *data2, const int len2, int where)
{
    struct line *line;

    if (!text) return;

    line = make2_new_line(data1, len1, data2, len2);

    if (!insert_line_internal(text, line, where)) {
	/* Unable to insert, must have been bad line number. */
	free_line(line);
    }
}


/* Delete out from given line to line. */
static void delete_lines_internal(struct text *text, const int start_pos, struct line *start, struct line *end)
{
    struct line *prev;
    struct line *next;
    struct line *temp;

    if (!start || !end) return;

    /* Just in front of where we started deleting, and just after. */
    prev = start->prev;
    next = end->next;

    /* Now clean up the first and last pointers. */
    if (prev == NULL) text->first = next;
    else prev->next = next;
    if (next == NULL) text->last = prev;
    else next->prev = prev;

    /* Setup the current line pointer to just after deleted lines.  If
       deletion went to end of text, just leave current at NULL and let
       it get moved on next access. */
    text->curr = next;
    text->curr_line = next ? start_pos : 0;

    /* Free up the lines. */
    end->next = NULL;
    for (temp = start; temp; temp = next) {
	next = temp->next;
	text->nbytes -= temp->length; /* Subtract off bytes from total. */
	text->total_lines--;
	free_line(temp);
    }
}

/* Flush lines until at least amount bytes have been flushed, or the end has
   been reached. */
void flush_text(struct text *text, int where, int amount)
{
    struct line *start, *end;

    if (amount <= 0) return;

    where = translate_line(text, where);

    start = find_line(text, where);

    for (end = start; end && (amount > 0); end = end->next) {
	amount -= end->length;
    }

    delete_lines_internal(text, where, start, end);
}


/* Delete lines from start to end inclusive. */
void delete_text(struct text *text, int start_pos, int end_pos)
{
    struct line *start, *end;

    if (!text) return;

    start_pos = translate_line(text, start_pos); /* Check for -N notation. */
    end_pos = translate_line(text, end_pos); /* Check for -N notation. */

    if (start_pos > end_pos) return;

    /* Get pointers to the first and last lines to delete. */
    start = find_line(text, start_pos);
    end = find_line(text, end_pos);

    delete_lines_internal(text, start_pos, start, end);
}


const char *text_line(struct text *text, int where, int *length)
{
    struct line *line;

    if (!text) return NULL;

    where = translate_line(text, where); /* Check for -N. */
    line = find_line(text, where);

    if (line) {
	if (length) *length = line->length;
	return line->data;
    } else {
	if (length) *length = 0;
	return NULL;
    }
}


int text_total_lines(struct text *text)
{
    return text ? text->total_lines : 0;
}


int text_total_bytes(struct text *text)
{
    return text ? text->nbytes : 0;
}