ftp.nice.ch/pub/next/unix/editor/ne-1.0.NI.s.tar.gz#/ne-1.0.NI.s/src/buffer.c

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

/* Buffer handling functions, including allocation, deallocation, and I/O.

   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 2, 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"

#ifndef _AMIGA
#include <sys/file.h>
#else
#include <proto/exec.h>
#define getpid() FindTask(NULL)
#define S_IRUSR 0
#define S_IWUSR 0
#endif

/* The standard pool allocation dimension. */

#define STD_POOL_SIZE (16*1024)

/* The standard line descriptor pool allocation dimension (in lines). */

#define STD_LINE_DESC_POOL_SIZE (512)

/* The amount by which we increment the first pool dimension, with respect
to the size of the given file. */

#define STANDARD_INCREMENT (8*1024)

/* The number of lines by which we increment the first line descriptor
pool dimension, with respect to the number of lines of the given file. */

#define STANDARD_LINE_INCREMENT (256)

/* The maximum number of spaces which are allocated on the stack while
performing an insert_spaces (if more spaces are needed, malloc() is used). */

#define MAX_STACK_SPACES (128)

/* The length of the block used in order to optimize saves. */

#define SAVE_BLOCK_LEN   (32*1024)

/* It seems that, on some systems, "\0" does NOT produce a two-NULLs
string---only one NULL is produced instead. Incredible. */

static char two_nulls[4] = { '\0', '\0', '\0', '\0' };




/* These functions allocate and deallocate character pools. The size of the
pool is the number of characters, and it is forced to be at least
STD_POOL_SIZE. */


char_pool *alloc_char_pool(int size) {

	char_pool *cp;

	if (size < STD_POOL_SIZE) size = STD_POOL_SIZE;

	if (cp = calloc(1, sizeof(char_pool))) {
		if (cp->pool = calloc(sizeof(char), size)) {
			cp->size = size;
			return(cp);
		}
		free(cp);
	}
	return(NULL);
}

void free_char_pool(char_pool *cp) {

	if (cp == NULL) return;

	free(cp->pool);
	free(cp);
}



/* Given a pointer in a character pool and a buffer, this function returns
the respective pool. It can return NULL if the pointer wasn't in any pool,
but this condition denotes a severe malfunctioning. */

char_pool *get_char_pool(buffer *b, char *p) {

	char_pool *cp;

	cp = (char_pool *)b->char_pool_list.head;

	while(cp->cp_node.next) {
		assert_char_pool(cp);

		if (p >= cp->pool && p < cp->pool+cp->size) return(cp);
		cp = (char_pool *)cp->cp_node.next;
	}

	assert(FALSE);

	return(NULL);
}




/* These functions allocate and deallocate line descriptor pools. The size
of the pool is the number of lines, and is forced to be at least
STD_LINE_DESC_POOL_SIZE. */


line_desc_pool *alloc_line_desc_pool(int pool_size) {

	line_desc_pool *ldp;

	if (pool_size < STD_LINE_DESC_POOL_SIZE) pool_size = STD_LINE_DESC_POOL_SIZE;

	if (ldp = calloc(1, sizeof(line_desc_pool))) {
		if (ldp->pool = calloc(pool_size, sizeof(line_desc))) {
			int i;

			ldp->size = pool_size;
			new_list(&ldp->free_list);
			for(i=0; i<pool_size; i++) add_tail(&ldp->free_list, &ldp->pool[i].ld_node);
			return(ldp);
		}
		free(ldp);
	}

	return(NULL);

}

void free_line_desc_pool(line_desc_pool *ldp) {

	if (ldp == NULL) return;

	assert_line_desc_pool(ldp);

	free(ldp->pool);
	free(ldp);
}



/* These functions allocate and deallocate a buffer. Note that on allocation
we have to initialize the list pointers, and on dellocation we have to free
all the lists. Moreover, on allocation a buffer pointer can be passed so
that the new buffer can inherit various user flags. */

buffer *alloc_buffer(buffer *cur_b) {

	buffer *b;

	if (b = calloc(1, sizeof(buffer))) {

		new_list(&b->line_desc_pool_list);
		new_list(&b->line_desc_list);
		new_list(&b->char_pool_list);

		b->cur_macro = alloc_char_stream(0);
		b->tab_size = 8;

		b->status_bar = b->insert = b->verbose_macros = b->do_undo = b->auto_prefs = 1;

		if (cur_b) {

			b->tab_size = cur_b->tab_size;
			b->right_margin = cur_b->right_margin;
			b->turbo = cur_b->turbo;
			b->cur_clip = cur_b->cur_clip;

			b->free_form = cur_b->free_form;
			b->status_bar = cur_b->status_bar;
			b->fast_gui = cur_b->fast_gui;
			b->word_wrap = cur_b->word_wrap;
			b->auto_indent = cur_b->auto_indent;
			b->verbose_macros = cur_b->verbose_macros;
			b->do_undo = cur_b->do_undo;
			b->auto_prefs = cur_b->auto_prefs;
			b->case_search = cur_b->case_search;
			b->no_file_req = cur_b->no_file_req;
			b->binary = cur_b->binary;

		}
		return(b);
	}

	return(NULL);
}

/* This function is useful when resetting a buffer, but not really
destroying it. Since it modifies some lists, it cannot be interrupted
from a signal. Note that the search, replace and command_line strings are
not cleared. */

void free_buffer_contents(buffer *b) {

	if (!b) return;

	block_signals();

	free_list(&b->line_desc_pool_list, free_line_desc_pool);
	free_list(&b->char_pool_list, free_char_pool);
	new_list(&b->line_desc_list);

	b->allocated_chars = b->free_chars = 0;

	free_char_stream(b->last_deleted);
	b->last_deleted = NULL;

	free(b->filename);
	b->filename = NULL;

	reset_undo_buffer(&b->undo);
	b->buffer_is_modified = b->marking = b->recording = b->x_wanted = 0;

	release_signals();
}


/* This function removes all data in a buffer, but leaves in the current
macro, the search, replace and command_line strings, and an empty line. */

void clear_buffer(buffer *b) {

	line_desc *ld;

	if (!b) return;

	block_signals();

	free_buffer_contents(b);

	ld = alloc_line_desc(b);
	add_head(&b->line_desc_list, &ld->ld_node);

	b->line_num = 1;

	reset_position_to_sof(b);

	assert_buffer(b);

	release_signals();
}


/* This functions frees all the data associated to a buffer. */

void free_buffer(buffer *b) {

	if (b == NULL) return;

	assert_buffer(b);

	free_buffer_contents(b);

	free_char_stream(b->cur_macro);

	free(b->find_string);
	free(b->replace_string);
	free(b->command_line);

	free(b);
}


/* This function computes how many characters have been "lost" in a buffer,
i.e., how many free characters lie inside the first and last used characters
of the character pools. This characters can only be allocated by
alloc_chars_around(). */

int calc_lost_chars(buffer *b) {

	int n = 0;

	char_pool *cp = (char_pool *)b->char_pool_list.head;

	while(cp->cp_node.next) {

		n += cp->size-(cp->last_used-cp->first_used+1);
		cp = (char_pool *)cp->cp_node.next;
	}

	return(b->free_chars-n);
}


/* This function returns the nth buffer in the global buffer list, or
NULL if less than n buffers are available. */

buffer *get_nth_buffer(int n) {

	buffer *b = (buffer *)buffers.head;

	while(b->b_node.next) {
		if (!n--) return(b);
		b = (buffer *)b->b_node.next;
	}
	return(NULL);
}



/* This function returns a buffer, given its name (i.e., the name of the
file it contains). Note that file_part() is applied *both* to the
string passed *and* to the buffer names, so that the path is immaterial. */

buffer *get_buffer_named(const char *p) {

	buffer *b = (buffer *)buffers.head;

	if (!p) return(NULL);

	p = file_part((char *)p);

	while(b->b_node.next) {
		if (b->filename && !strcmp(file_part(b->filename), p)) return(b);
		b = (buffer *)b->b_node.next;
	}

	return(NULL);
}



/* This function returns TRUE if any of the buffers has been modified since
the last save. */

int modified_buffers(void) {

	buffer *b = (buffer *)buffers.head;

	while(b->b_node.next) {
		if (b->buffer_is_modified) return(TRUE);
		b = (buffer *)b->b_node.next;
	}
	return(FALSE);
}



/* This function saves all buffers which have been modified since the last
save. Returns an error if a save is unsuccessful, or if a buffer has no
name. */

int save_all_modified_buffers(void) {

	buffer *b = (buffer *)buffers.head;

	while(b->b_node.next) {
		if (b->buffer_is_modified)
			if (save_buffer_to_file(b, NULL)) return(ERROR);
		b = (buffer *)b->b_node.next;
	}
	return(0);
}



/* Now we have the much more sophisticated allocation functions which create
small elements such as lines and line descriptors. The strategy is rather
complex. All the operations are in the context of a given buffer. Most of
these functions are protected internally against being interrupted by
signals, since auto_save could die miserably because of the inconsistent
state of a list. */


/* This function allocates a line descriptor from the pools available
in the given buffer. A new pool is allocated and linked if necessary. */


line_desc *alloc_line_desc(buffer *b) {
	line_desc_pool *ldp;
	line_desc *ld;

	ldp = (line_desc_pool *)b->line_desc_pool_list.head;

	block_signals();

	while(ldp->ldp_node.next) {

		assert_line_desc_pool(ldp);

		if (ldp->free_list.head->next) {

			ld = (line_desc *)ldp->free_list.head;

			rem(&ld->ld_node);

			if (!ldp->free_list.head->next) {
				rem(&ldp->ldp_node);
				add_tail(&b->line_desc_pool_list, &ldp->ldp_node);
			}

			ldp->allocated_items++;

			ld->line = NULL;
			ld->line_len = 0;
			release_signals();
			return(ld);
		}
		ldp = (line_desc_pool *)ldp->ldp_node.next;
	}

	/* No chances, all pools are full. Let's allocate a new one,
	using the standard pool size, and let's put it at the start
	of the list, so that it is always scanned first. */

	if (ldp = alloc_line_desc_pool(0)) {
		add_head(&b->line_desc_pool_list, &ldp->ldp_node);
		rem(&(ld = (line_desc *)ldp->free_list.head)->ld_node);
		ldp->allocated_items = 1;
		release_signals();
		return(ld);
	}

	release_signals();
	return(NULL);
}



/* This function frees a line descriptor, (and the line descriptor pool
containing it, should it become empty). */

void free_line_desc(buffer *b, line_desc *ld) {

	line_desc_pool *ldp;

	ldp = (line_desc_pool *)b->line_desc_pool_list.head;

	assert(ldp != NULL);

	/* We scan the pool list in order to find where the given
	line descriptor lives. */

	while(ldp->ldp_node.next) {
		assert_line_desc_pool(ldp);

		if (ld >= ldp->pool && ld < ldp->pool+ldp->size) break;
		ldp = (line_desc_pool *)ldp->ldp_node.next;
	}

	assert(ldp->ldp_node.next != NULL);

	block_signals();

	add_head(&ldp->free_list, &ld->ld_node);

	if (--ldp->allocated_items == 0) {
		rem(&ldp->ldp_node);
		free_line_desc_pool(ldp);
	}

	release_signals();
}


/* This function allocates len characters from the character pools of the
given buffer. If necessary, a new pool is allocated. */

char *alloc_chars(buffer *b, int len) {

	char_pool *cp;

	if (!len || !b) return(NULL);

	assert_buffer(b);

	cp = (char_pool *)b->char_pool_list.head;

	assert(cp != NULL);

	block_signals();

	while(cp->cp_node.next) {
		assert_char_pool(cp);

		/* We try to allocate before the first used character,
		or after the last used character. If we succeed with a
		pool which is not the head of the list, we move it to
		the head in order to optimize the next try. */

		if (cp->first_used>=len) {

			cp->first_used -= len;
			b->free_chars -= len;

			if (cp != (char_pool *)b->char_pool_list.head) {
				rem(&cp->cp_node);
				add_head(&b->char_pool_list, &cp->cp_node);
			}

			release_signals();
			return(cp->pool+cp->first_used);
		}
		else if (cp->size-cp->last_used > len) {

			cp->last_used += len;
			b->free_chars -= len;

			if (cp != (char_pool *)b->char_pool_list.head) {
				rem(&cp->cp_node);
				add_head(&b->char_pool_list, &cp->cp_node);
			}

			release_signals();
			return(cp->pool+cp->last_used-len+1);
		}
		cp = (char_pool *)cp->cp_node.next;
	}

	/* If no free space has been found, we allocate a new pool which is guaranteed
	to contain at least len characters. The pool is added to the head of the list. */

	if (cp = alloc_char_pool(len)) {
		add_head(&b->char_pool_list, &cp->cp_node);
		cp->last_used = len-1;

		b->allocated_chars += cp->size;
		b->free_chars += cp->size-len;

		release_signals();
		return(cp->pool);
	}

	release_signals();
	return(NULL);
}



/* This function is very important, since it embeds all the philosophy
behind ne's character pool management. It performs an allocation *locally*,
i.e., it tries to see if there are enough free characters around the line
pointed to by a line descriptor by looking at non-nullness of surrounding
characters (if a character is set to 0, it is free). First the characters
after the line are checked, then the characters before (this can be reversed
via the check_first_before flag). The number of characters available *after*
the line is returned, or ERROR if the allocation failed. The caller can recover
the characters available before the line since he knows the length of the
allocation. Note that it is *only* through this function that the "lost"
characters can be allocated, but being editing a local activity, this is
what happens usually. */


int alloc_chars_around(buffer *b, line_desc *ld, int n, int check_first_before) {

	char *before, *after;
	char_pool *cp;

	assert(ld->line != NULL);

	cp = get_char_pool(b, ld->line);

	assert_char_pool(cp);

	block_signals();

	before = ld->line-1;
	after = ld->line+ld->line_len;

	if (check_first_before) {
		while(before >= cp->pool && !*before && (ld->line-1)-before<n)
			before--;
		while(after < cp->pool + cp->size && !*after && (after-(ld->line+ld->line_len))+((ld->line-1)-before)<n)
			after++;
	}
	else {
		while(after < cp->pool + cp->size && !*after && after-(ld->line+ld->line_len)<n)
			after++;
		while(before >= cp->pool && !*before && (after-(ld->line+ld->line_len))+((ld->line-1)-before)<n)
			before--;
	}

	assert(((ld->line-1)-before)+(after-(ld->line+ld->line_len)) <= n);
	assert(((ld->line-1)-before)+(after-(ld->line+ld->line_len)) >= 0);

	if (((ld->line-1)-before)+(after-(ld->line+ld->line_len)) == n) {
		if (cp->pool+cp->first_used == ld->line) cp->first_used = (before+1)-cp->pool;
		if (cp->pool+cp->last_used == ld->line+ld->line_len-1) cp->last_used = (after-1)-cp->pool;
		b->free_chars -= n;

		release_signals();
		return(after-(ld->line+ld->line_len));
	}

	release_signals();
	return(ERROR);
}



/* This function frees a block of len characters pointed to by p. If the
char pool containing the block becomes completely free, it is removed from
the list. */

void free_chars(buffer *b, char *p, int len) {

	char_pool *cp;

	if (!b || !p || !len) return;

   cp = get_char_pool(b, p);

   assert_char_pool(cp);

   assert(*p);
   assert(p[len-1]);

	block_signals();

   memset(p, 0, len);
   b->free_chars += len;

	if (p == &cp->pool[cp->first_used]) while(!cp->pool[cp->first_used] && cp->first_used <= cp->last_used) cp->first_used++;
	if (p+len-1 == &cp->pool[cp->last_used]) while(!cp->pool[cp->last_used] && cp->first_used <= cp->last_used) cp->last_used--;

	if (cp->last_used < cp->first_used) {
		rem(&cp->cp_node);
		b->allocated_chars -= cp->size;
		b->free_chars -= cp->size;
		free_char_pool(cp);
		release_signals();
		return;
	}

	assert_char_pool(cp);
	release_signals();
}


/* The following functions represent the only legal way of modifying a
buffer. They are all based on insert_stream and delete_stream (except for
the I/O functions). A stream is a sequence of NULL-terminated strings. The
semantics associated is that each string is a separate line terminated by a
line feed, *except for the last one*. Thus, a NULL-terminated string is a
line with no linefeed. All the functions accept a position specified via
a line descriptor and a position (which is the offset to be applied to
the line pointer of the line descriptor). Also the line number is usually
supplied, since it is necessary for recording the operation in the
undo buffer. */


/* This function inserts a line at the current position. The effect
is obtained by inserting a stream of two NULLs. */


int insert_lin(buffer *b, line_desc *ld, int line, int pos) {
	return(insert_stream(b, ld, line, pos, two_nulls, 2));
}


/* This function deletes a whole line, putting it in the temporary line
buffer used by the UndelLine command. */

int delete_lin(buffer *b, line_desc *ld, int line) {

	int error;

	assert_line_desc(ld);
	assert_buffer(b);

	block_signals();

	if (ld->line_len) {
		if (b->last_deleted = reset_stream(b->last_deleted)) {
			add_to_stream(b->last_deleted, ld->line, ld->line_len);
			add_to_stream(b->last_deleted, two_nulls, 2);
		}
	}

	/* We delete a line by delete_stream()ing its length plus one. However,
	if we are on the last line of text, there is no terminating line feed. */

	error = delete_stream(b, ld, line, 0, ld->line_len+(ld->ld_node.next->next ? 1 : 0));
	release_signals();

	return(error);
}


/* This function undeletes the last deleted line, using the last_deleted stream. */

int undelete_line(buffer *b) {

	if (!b->last_deleted) return(ERROR);

	insert_stream(b, b->cur_line_desc, b->cur_line, b->cur_pos, b->last_deleted->stream, b->last_deleted->len);

	return(OK);
}



/* This function deletes a line up to its end. */

void delete_to_eol(buffer *b, line_desc *ld, int line, int pos) {

	if (!ld || pos >= ld->line_len) return;

	delete_stream(b, ld, line, pos, ld->line_len-pos);
}



/* This function inserts a character stream in a line at a given position.
The position has to be smaller or equal to the line length. Since the stream
can contain many lines, this function can be used for manipulating all
insertions. It also record the inverse operation in the undo buffer if
do_undo is TRUE. */


int insert_stream(buffer *b, line_desc *ld, int line, int pos, const char *stream, int stream_len) {

	int i, len;
	char *p;
	const char *s = stream;

	if (!b || !ld || !stream || stream_len<2 || pos>ld->line_len) return(ERROR);

	assert_buffer(b);
	assert_line_desc(ld);

	block_signals();

	if (b->do_undo && !(b->undoing || b->redoing))
		add_undo_step(b, line, pos, -stream_len+1);

	while(s-stream < stream_len) {

		if (len = strlen(s)) {

			/* First case; there is no character allocated on this line. We
			have to freshly allocate the line. */

			if (!ld->line) {
				if (ld->line = alloc_chars(b, len)) {
					memcpy(ld->line, s, len);
					ld->line_len = len;
				}
				else {
					release_signals();
					return(OUT_OF_MEMORY);
				}
			}


			/* Second case. There are not enough characters around ld->line. Note
			that the value of the check_first_before parameter depends on
			the position at which the insertion will be done, and it is chosen
			in such a way to minimize the number of character to move. */

			else if ((i = alloc_chars_around(b, ld, len, pos < ld->line_len/2))<0) {
				if (p = alloc_chars(b, ld->line_len+len)) {
					memcpy(p, ld->line, pos);
					memcpy(&p[pos], s, len);
					memcpy(&p[pos+len], ld->line+pos, ld->line_len-pos);
					free_chars(b, ld->line, ld->line_len);
					ld->line = p;
					ld->line_len += len;
				}
				else {
					release_signals();
					return(OUT_OF_MEMORY);
				}
			}


			/* Third case. There are enough free characters around ld->line. */

			else {
				if (len-i) memmove(ld->line-(len-i), ld->line, pos);
				if (i) memmove(ld->line+pos+i, ld->line+pos, ld->line_len-pos);
				memcpy(ld->line-(len-i)+pos, s, len);

				ld->line -= (len-i);
				ld->line_len += len;
			}
			b->buffer_is_modified = 1;
		}

		s += len+1;

		/* If the string we have inserted is not the last one, we create
		a new line under the current one and set ld to point to it. */

		if (s != stream+stream_len) {
			line_desc *new_ld;

			if (new_ld = alloc_line_desc(b)) {

				add(&new_ld->ld_node, &ld->ld_node);
				b->line_num++;

				if (pos+len < ld->line_len) {
					new_ld->line_len = ld->line_len - pos - len;
					new_ld->line = &ld->line[pos+len];
					ld->line_len = pos+len;
					if (pos+len == 0) ld->line = NULL;
				}

				b->buffer_is_modified = 1;
				ld = new_ld;
				pos = 0;
			}
			else {
				release_signals();
				return(OUT_OF_MEMORY);
			}
		}
	}

	release_signals();
	return(OK);
}



/* This function inserts a single character (it creates a suitable temporary
stream). */


int insert_char(buffer *b, line_desc *ld, int line, int pos, char c) {

	char t[2];

	t[0] = c;
	t[1] = 0;

	assert(c != 0);

	return(insert_stream(b, ld, line, pos, t, 2));
}




/* This function inserts a number of spaces. If the number is less than
MAX_STACK_SPACES, the space[] array is used; otherwise, a temporary block of
memory is malloc()ated. */

int insert_spaces(buffer *b, line_desc *ld, int line, int pos, int n) {

	int result;
	char spaces[MAX_STACK_SPACES];
	char *t;

	if (n>=MAX_STACK_SPACES) t = malloc(n+1);
	else t = spaces;

	if (!t) return(FALSE);

	memset(t, ' ', n);
	t[n] = 0;

	result = insert_stream(b, ld, line, pos, t, n+1);

	if (t != spaces) free(t);

	return(result);
}



/* This function deletes a stream of len characters, i.e., deletes len
characters from the given position, counting line feeds as a character. The
operation is recorded in the undo buffer. */

int delete_stream(buffer *b, line_desc *ld, int line, int pos, int len) {

	int n, m;

	assert_buffer(b);
	assert_line_desc(ld);

	/* If we are in no one's land, we return. */

	if (!b || !ld || !len || pos > ld->line_len ||
		pos == ld->line_len && !ld->ld_node.next->next) return(ERROR);

	block_signals();

	if (b->do_undo && !(b->undoing || b->redoing))
		add_undo_step(b, line, pos, len+1);

	while(len) {

		/* First case: we are just on the end of a line. We join the current line with
		the following one (if it's there of course). */

		if (pos == ld->line_len) {
			char *p;
			line_desc *next_ld = (line_desc *)ld->ld_node.next;

			assert(next_ld != NULL);
			assert_line_desc(next_ld);

			/* If one of the lines is empty, or their contents are adjacent,
			we simply set a pointer. */

			if (!ld->line || !next_ld->line || ld->line+ld->line_len == next_ld->line) {

				if (!ld->line) ld->line = next_ld->line;

			}

			/* We try to allocate characters around one line or the other
			one; if we fail, we allocate enough space for both lines elsewhere. */

			else if ((n = alloc_chars_around(b, ld, next_ld->line_len, FALSE))<0 && (m = alloc_chars_around(b, next_ld, ld->line_len, TRUE))<0) {
				if (p = alloc_chars(b, ld->line_len+next_ld->line_len)) {

					memcpy(p, ld->line, ld->line_len);
					memcpy(p+ld->line_len, next_ld->line, next_ld->line_len);

					free_chars(b, ld->line, ld->line_len);
					free_chars(b, next_ld->line, next_ld->line_len);

					ld->line = p;
				}
				else {
					release_signals();
					return(OUT_OF_MEMORY);
				}
			}

			/* In case one of the alloc_chars_around succeeds, we have just to
			move the lines in the right place. */

			else if (n>=0) {
				if (n < next_ld->line_len) memmove(ld->line + (n-next_ld->line_len), ld->line, ld->line_len);
				ld->line += (n-next_ld->line_len);
				memcpy(ld->line+ld->line_len, next_ld->line, next_ld->line_len);

				free_chars(b, next_ld->line, next_ld->line_len);
			}
			else {
				if (m) memmove(next_ld->line+m, next_ld->line, next_ld->line_len);
				next_ld->line += m;
				memcpy(next_ld->line-ld->line_len, ld->line, ld->line_len);

				free_chars(b, ld->line, ld->line_len);

				ld->line = next_ld->line-ld->line_len;
			}

			ld->line_len += next_ld->line_len;
			b->line_num--;

			rem(&next_ld->ld_node);
			free_line_desc(b, next_ld);

			len--;
			if (!b->redoing) {
				if (b->undoing) add_to_stream(&b->undo.redo, "", 1);
				else if (b->do_undo) add_to_undo_stream(&b->undo, "", 1);
			}
		}

		/* Second case: we are inside a line. We delete len characters or,
		if there are less then len characters to delete, we delete up to
		the end of the line. The number of characters to move is minimized. */

		else {
			n = len > ld->line_len-pos ? ld->line_len-pos : len;

			if (!b->redoing) {
				if (b->undoing) add_to_stream(&b->undo.redo, &ld->line[pos], n);
				else if (b->do_undo) add_to_undo_stream(&b->undo, &ld->line[pos], n);
			}

			if (pos < ld->line_len/2) {
				memmove(ld->line+n, ld->line, pos);
				free_chars(b, ld->line, n);
				ld->line += n;
			}
			else {
				memmove(ld->line+pos, ld->line+pos+n, ld->line_len-pos-n);
				free_chars(b, &ld->line[ld->line_len-n], n);
			}

			if (!(ld->line_len -= n)) ld->line = NULL;

			len -= n;

			assert_line_desc(ld);
		}
		b->buffer_is_modified = 1;
	}

	if (!b->redoing) {
		if (b->undoing) add_to_stream(&b->undo.redo, "", 1);
		else if (b->do_undo) add_to_undo_stream(&b->undo, "", 1);
	}

	release_signals();
	return(OK);
}


/* This function deletes a single character. */

int delete_char(buffer *b, line_desc *ld, int line, int pos) {
	return(delete_stream(b, ld, line, pos, 1));
}



/* This function changes the buffer file name to the given string, which
must have been obtained through malloc(). */

void change_filename(buffer *b, char *name) {

	assert(name != NULL);

	if (b->filename) free(b->filename);
	b->filename = name;
}




/* Here we load a file into a given buffer. The buffer lists are deallocated
first. If there is not write access to the file, the read-only flag is set.
Note that we consider line feeds 0x0A's, 0x0D's and 0x00's (the last being
made necessary by the way the pools are handled), unless the binary flag is
set, in which case we consider only the 0x00's. */


int load_file_in_buffer(buffer *b, const char *name) {
	int fh, result;

	if (!b) return(ERROR);

	assert_buffer(b);

	if ((fh = open(name = tilde_expand(name), O_RDONLY)) >= 0) {
		result = load_fh_in_buffer(b, fh);
		close(fh);
		if (!result) b->read_only = (access(name, W_OK) != 0);
		return(result);
	}

	return(CANT_OPEN_FILE);
}



/* This function, together with insert_stream and delete_stream, is the only
way of modifying the contents of a buffer. While loading a file could have
passed through insert_stream, it would have been intolerably slow for large
files. The flexible pool struture of ne allows to load the file with a
single read in a big pool. */

int load_fh_in_buffer(buffer *b, int fh) {

	int i, len, line_num;
	char *p;
	char_pool *cp;
	line_desc_pool *ldp;

	len = lseek(fh, 0, SEEK_END);

	if (len < 0 || lseek(fh, 0, SEEK_SET)<0) return(IO_ERROR);

	free_buffer_contents(b);

	if (!len) {
		clear_buffer(b);
		return(OK);
	}

	block_signals();

	if (cp = alloc_char_pool(len+STANDARD_INCREMENT)) {

		if (read(fh, cp->pool, len) < len) {
			free_char_pool(cp);
			release_signals();
			return(IO_ERROR);
		}

		b->allocated_chars = cp->size;
		b->free_chars = cp->size-len;

		p = cp->pool;

		/* The line feeds become free characters, because we do not
		need them. In the mean time, we count the number of lines. */

		for(line_num=i=0; i<len; i++,p++)
			if (!b->binary && (*p == 0x0A || *p == 0x0D) || !*p) {
				*p = 0;
				line_num++;
				b->free_chars++;
			}

		line_num++;


		if (ldp = alloc_line_desc_pool(line_num+STANDARD_LINE_INCREMENT)) {

			p = cp->pool;

			for(i=0; i<line_num; i++) {
				rem(&ldp->pool[i].ld_node);
				add_tail(&b->line_desc_list, &ldp->pool[i].ld_node);
				if (*p) {
					ldp->pool[i].line = p;
					ldp->pool[i].line_len = strlen(p);
				}

				while(*p++);
			}

			ldp->allocated_items = line_num;

			/* We set correctly the offsets of the first and last character used. If no
			character is used (i.e., we have a file of line feeds), the char pool is
			freed. */

			if (b->free_chars < b->allocated_chars) {
				cp->last_used = len;
				while(!cp->pool[cp->first_used]) cp->first_used++;
				while(!cp->pool[cp->last_used]) cp->last_used--;
				add_head(&b->char_pool_list, &cp->cp_node);

				assert_char_pool(cp);
			}
			else free_char_pool(cp);

			add_head(&b->line_desc_pool_list, &ldp->ldp_node);

			b->line_num = line_num;

			reset_position_to_sof(b);

			release_signals();
			return(OK);
		}
		free_char_pool(cp);
	}

	release_signals();
	return(OUT_OF_MEMORY);
}



/* Here we save a buffer to a given file. If no file is specified, the
buffer filename field is used. The buffer_is_modified flag is set to 0. */


int save_buffer_to_file(buffer *b, const char *name) {

	int fh, error = OK;
	char *p, line_terminator;

	line_desc *ld = (line_desc *)b->line_desc_list.head;

	if (!b) return(ERROR);

	assert_buffer(b);

	if (name == NULL) name = b->filename;

	if (!name) return(ERROR);

	block_signals();

	line_terminator = b->binary ? 0 : '\n';

	if ((fh = open(tilde_expand(name), O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR)) >= 0) {

		/* If we can allocate SAVE_BLOCK_LEN bytes, we will use
		them as a buffer for our saves. */

		if (p = malloc(SAVE_BLOCK_LEN)) {

			/* used keeps track of the number of bytes used in the buffer. l, len
			specify the pointer to the block of characters to save, and
			its length. In case of very long lines, or buffer border crossing,
			they could point in the middle of a line descriptor. */

			int used = 0, len;
			char *l;

			while(ld->ld_node.next) {
				l = ld->line;
				len = ld->line_len;

				while(len>0) {
					if (SAVE_BLOCK_LEN-used > len) {
						memcpy(p+used, l, len);
						used += len;
						len = 0;
					}
					else {
						memcpy(p+used, l, SAVE_BLOCK_LEN-used);
						len -= SAVE_BLOCK_LEN-used;
						l += SAVE_BLOCK_LEN-used;

						used = 0;

						if (write(fh, p, SAVE_BLOCK_LEN) < SAVE_BLOCK_LEN) {
							error = IO_ERROR;
							break;
						}
					}
				}

				ld = (line_desc *)ld->ld_node.next;

				/* Note that the two previous blocks never leave used == SAVE_BLOCK_LEN.
				Thus, we can always assume there is a free byte at p+used. */

				if (ld->ld_node.next) p[used++] = line_terminator;
			}

			if (used && write(fh, p, used) < used) error = IO_ERROR;

			free(p);
		}
		else {

			/* If the buffer is not available, just save line by line. */

			while(ld->ld_node.next) {

				if (ld->line) {
					if (write(fh, ld->line, ld->line_len) < ld->line_len) {
						error = IO_ERROR;
						break;
					}
				}

				ld = (line_desc *)ld->ld_node.next;

				if (ld->ld_node.next) {
					if (write(fh, &line_terminator, 1) < 1) {
						error = IO_ERROR;
						break;
					}
				}
			}
		}

		if (close(fh)) error = IO_ERROR;
		if (error == OK) b->buffer_is_modified = 0;

	}
	else error = CANT_OPEN_FILE;

	release_signals();
	return(error);
}


/* This function autosaves a given buffer. If the buffer has a name, a '#'
is prefixed to it. If the buffer has no name, a fake name is generated using
the PID of ne and the pointer to the buffer structure. This ensures
uniqueness. Autosave never writes on the original file, also because it can
be called during an emergency exit caused by a signal. */


void auto_save(buffer *b) {

	char *p;

	if (b->buffer_is_modified) {
		if (b->filename) {
			if (p = malloc(strlen(file_part(b->filename))+2)) {
				strcpy(p, "#");
				strcat(p, file_part(b->filename));
			}
		}
		else {
			if (p = malloc(MAX_INT_LEN*2))
				sprintf(p, "%x.%x", b, getpid());
		}
		save_buffer_to_file(b, p);
		free(p);
	}
}

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