ftp.nice.ch/pub/next/unix/editor/jed.N.bs.tar.gz#/jed.N.bs/src/paste.c

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

/*
 *  Copyright (c) 1992, 1995 John E. Davis  (davis@space.mit.edu)
 *  All Rights Reserved.
 */
    
#include <config.h>

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

#include "buffer.h"
#include "ins.h"
#include "line.h"
#include "paste.h"
#include "screen.h"
#include "misc.h"
#include "cmds.h"

/* SLang user object be larger than 128 */
#define JED_MARK_TYPE 128

Buffer *Paste_Buffer;
Buffer *Rectangle_Buffer;



/* This is used by the narrow command so that multiple windows are 
 * handled properly. The assumption is that we are dealing with a canonical
 * region */
      
static void touch_windows(void)
{
   Window_Type *w;
   Line *l;
   unsigned int n;
   
   w = JWindow;
   JWindow = JWindow->next;
   while (JWindow != w)
     {
	if (CBuf == JWindow->buffer)
	  {
	     /* The mark is set with line at top of buffer */
	     l = CBuf->marks->line;
	     n = CBuf->marks->n;
	     while ((l != NULL) && (l != JWindow->mark.line)) l = l->next, n++;
	     if (l == NULL) 
	       {
		  JWindow->mark.line = CLine;
		  JWindow->mark.point = Point;
		  JWindow->mark.n = n - 1;
	       }
	     touch_window();
	  }
	JWindow = JWindow->next;
     }
   pop_mark(&Number_Zero);
}


#if 0
static int line_in_buffer(Line *line)
{
   Line *l;
   
   l = CBuf->beg;
   while (l != NULL) if (l == line) return(1); else l = l->next;
   return(0);
}
#endif


/* with prefix argument, pop marks */
int set_mark_cmd()
{
   Mark *m;
   if (Repeat_Factor != NULL)
     {
	while (CBuf->marks != NULL) pop_mark(&Number_Zero);
	Repeat_Factor = NULL;
	return(1);
     }
   
   if (CBuf->marks == NULL)
     {
	push_mark();
     }
   m = CBuf->marks;
   m->line = CLine;
   m->point = Point;
   m->n = LineNum + CBuf->nup;
   if ((m->flags & VISIBLE_MARK) == 0) 
     {
	m->flags |= VISIBLE_MARK;
	CBuf->vis_marks++;
     }
   
   /* if (m != CBuf->marks) m->next = CBuf->marks;
    CBuf->marks = m; */
   if (Last_Key_Function == (FVOID_STAR) set_mark_cmd) message("Mark Set.");
   return(1);
}

int push_spot()
{
    Mark *m;

   if (CBuf->spot_ptr < SPOT_ARRAY_SIZE)
     {
	m = &CBuf->spot_array[CBuf->spot_ptr++];
     }
   else if (NULL == (m = (Mark *) SLMALLOC(sizeof(Mark))))
      {
	  exit_error("push_spot: malloc error!", 0);
      }

   m->line = CLine;
   m->point = Point;
   m->n = LineNum + CBuf->nup;
   m->next = CBuf->spots;
   CBuf->spots = m;
   return(1);
}

int push_mark()
{
    Mark *m;

   if (CBuf->mark_ptr < SPOT_ARRAY_SIZE)
     {
	m = &CBuf->mark_array[CBuf->mark_ptr++];
     }
   else if (NULL == (m = (Mark *) SLMALLOC(sizeof(Mark))))
     {
	exit_error("push_mark: malloc error!", 0);
     }
   /*
    if (NULL == (m = (Mark *) SLMALLOC(sizeof(Mark))))
      {
	 msg_error("push_mark: malloc error!");
	 return(0);
      } */

   m->line = CLine;
   m->point = Point;
   m->n = LineNum + CBuf->nup;
   m->next = CBuf->marks;
   m->flags = 0;
   CBuf->marks = m;
   return(1);
}

void goto_mark(Mark *m)
{
   Line *l;
   
   l = m->line;
   LineNum = m->n;
	
   if (LineNum <= CBuf->nup) bob();
   else if (LineNum > CBuf->nup + Max_LineNum) eob();
   else
     {
	CLine = l;
	Point = m->point;
	LineNum -= CBuf->nup;
     }
}

int pop_mark(int *go)
{
   Mark *m;
   
   m = CBuf->marks;
   if (m == NULL) return(0);

   if (*go) goto_mark(m);
   if (m->flags & VISIBLE_MARK)
     {
	CBuf->vis_marks--;
	/* touch screen since region may be highlighted */
	if (CBuf->vis_marks == 0) touch_screen();
     }
   
   CBuf->marks = m->next;
   if (m == &CBuf->mark_array[CBuf->mark_ptr - 1])
     {
	CBuf->mark_ptr--;
     }
   else SLFREE(m);
   return(1);
}

int mark_spot()
{
    push_spot();
    message("Spot Marked.");
    return(1);
}

static int pop_spot_go (int go)
{
   Mark *m;
   
   m = CBuf->spots;
   if (m == NULL) return(0);
   
   if (go) goto_mark (m);
   
   CBuf->spots = m->next;
   
   if (m == &CBuf->spot_array[CBuf->spot_ptr - 1])
     {
	CBuf->spot_ptr--;
     }
   else SLFREE(m);
   return(1);
}

int pop_spot ()
{
   return pop_spot_go (1);
}

int exchange_point_mark(void)
{
   Line *save_line;
   int save_point;
   unsigned int save_n;
   Mark *m;
   
   if ((m = CBuf->marks) == NULL) return(0);
   
   save_point = Point;
   save_line = CLine;
   save_n = LineNum + CBuf->nup;
   
   goto_mark (m);
   
   m->point = save_point; m->line = save_line; m->n = save_n;
   return(1);
}

   
 /*returns 0 if the mark is not set and gives error.  Exchanges point and mark
  * to produce valid region.  A valid region is one with mark
  * earlier in the buffer than point.  Always call this if using a region
  * which reqires point > mark.  Also, push spot first then pop at end. 
  */
int check_region(int *push)
{
   register Line *beg, *tthis = CLine;
   int pbeg;

   if (CBuf->marks == NULL)
     {
	msg_error("Set mark first.");
	return(0);
     }

   if (*push) push_spot();
   beg = CBuf->marks->line;
   pbeg = CBuf->marks->point;

   if (beg == CLine)
     {
	if (pbeg <= Point) return(1);
     }

   else
     {
	while((beg != NULL) && (beg != tthis)) beg = beg->next;
	if (beg == tthis) return(1);
     }

   exchange_point_mark();
   return(1);
}


int widen_buffer_lines (Buffer *b)
{
   Narrow_Type *n;
   Buffer *save = CBuf;
   
   if (NULL == (n = b->narrow)) return(0);
   
   /* make sure buffer ends in final newline */
   
   switch_to_buffer(b);
   push_spot();
   eob();
   if ((n->end != NULL)
       && (!CLine->len || ('\n' != *(CLine->data + (CLine->len - 1)))))
     {
	unsigned int flags = CBuf->flags;
	ins('\n');
	CBuf->flags = flags;
     }
   
   pop_spot();
   
   if (n->end != NULL) n->end->prev = b->end;
   if (n->beg != NULL) n->beg->next = b->beg;
   b->end->next = n->end;
   b->beg->prev = n->beg;
   b->beg = n->beg1;
   if (n->end != NULL) b->end = n->end1;
   
   Max_LineNum += n->ndown + n->nup;
   LineNum += n->nup;
   
   /* adjust absolute offsets */
   b->nup -= n->nup;
   b->ndown -= n->ndown;
   b->narrow = n->next;
   
   SLFREE(n);
   switch_to_buffer(save);
   return(1);
}

int widen_buffer (Buffer *b)
{
   unsigned int flags;
   Buffer *save;
   
   if (b->narrow == NULL) return 0;

   if (b->narrow->is_region == 0)
     return widen_buffer_lines (b);
   
   flags = b->flags;
   b->flags &= ~READ_ONLY;
   
   save = CBuf;
   switch_to_buffer (b);
   push_spot ();
   bob ();
   push_spot ();
   eob ();
   widen_buffer_lines (CBuf);
   del ();
   pop_spot ();
   prevline (&Number_One);
   del ();
   pop_spot ();
   switch_to_buffer (save);
   
   b->flags = flags;
   return 1;
}

int narrow_to_region (void)
{
   int pnt;
   Line *line;
   unsigned int flags;
   
   if (0 == check_region (&Number_One))/* spot pushed */
     return 0;
   
   flags = CBuf->flags;
   CBuf->flags &= ~READ_ONLY;
   
   push_spot ();
   
   line = CLine;
   pnt = Point;
   
   pop_mark (&Number_One);
   /* Special case if region is empty */
   if ((CLine == line) && (pnt == Point))
     {
	pop_spot ();
	pop_spot ();
	newline ();
	push_mark ();

	newline ();
	prevline (&Number_One);

	if (narrow_to_lines ())
	  CBuf->narrow->is_region = 1;
     }
   else
     {
	newline ();
	push_mark ();
	pop_spot ();
	
	newline ();
	prevline (&Number_One);
	
	if (narrow_to_lines ())
	  CBuf->narrow->is_region = 1;
	
	pop_spot ();
     }
   
   CBuf->flags = flags;
   return 1;
}

int widen ()
{
   return widen_buffer_lines (CBuf);
}

int widen_region (void)
{
   return widen_buffer (CBuf);
}

/* not really a region of points but a region of lines. */
int narrow_to_lines ()
{
   Line *beg;
   Narrow_Type *nt;
   
   if (NULL == (nt = (Narrow_Type *) SLMALLOC(sizeof(Narrow_Type))))
     {
	msg_error("Malloc Error during narrow.");
	return(0);
     }
   
   if (!check_region(&Number_One)) return(0);       /* spot pushed */

   push_spot();
   pop_mark(&Number_One);
   push_mark();			       /* popped and used in touch_windows! */
   beg = CLine;
   nt->nup = LineNum - 1;
   
   pop_spot();  /* eor now */
   
   nt->ndown = Max_LineNum - LineNum;

   Max_LineNum = LineNum = LineNum - nt->nup;
   CBuf->nup += nt->nup;
   CBuf->ndown += nt->ndown;
   
   
   nt->next = CBuf->narrow;
   CBuf->narrow = nt;
   nt->beg = beg->prev;
   nt->end = CLine->next;
   nt->beg1 = CBuf->beg;
   nt->end1 = CBuf->end;
   
   nt->is_region = 0;
   CBuf->beg = beg;
   CBuf->end = CLine;
   beg->prev = NULL;
   CLine->next = NULL;
   
   if (CLine->len && (CLine->data[CLine->len - 1] == '\n'))
     {
	/* I do not think that this will affect undo. */
	CLine->len--;
     }
   
   pop_spot();
   touch_windows();
   return(1);
}

int yank()
{
   CHECK_READ_ONLY
    if (Paste_Buffer == NULL) return(0);
    insert_buffer(Paste_Buffer);
    return(1);
}

int copy_region_to_buffer(Buffer *b)
{
   int first_point, last_point, n, tmpm;
   Line *first, *last;
   Buffer *save_buffer;

   if (b->flags & READ_ONLY) 
     {
	msg_error(Read_Only_Error);
	return (0);
     }
   
   if (!check_region(&Number_One)) return(0);  /* spot pushed */
   last = CLine;
   last_point = Point;

   tmpm = 1; pop_mark(&tmpm);
   if (b == CBuf)
     {
	msg_error("A buffer cannot be inserted upon itself.");
	pop_spot();
	return(0);
     }

   first = CLine;
   first_point = Point;

   save_buffer = CBuf;
   switch_to_buffer(b);

   /* go through standard routines for undo comapatability */
   Suspend_Screen_Update = 1;
   if (first == last)
     {
	n = last_point - first_point;
	if (save_buffer == MiniBuffer)
	  {
	     ins_chars(first->data + first_point, n);
	  }
	else quick_insert(first->data + first_point, n);
     }
   else 
     {
	n = first->len - first_point;
	quick_insert(first->data + first_point, n);
	while (first = first->next, first != last)
	  {
	     quick_insert(first->data, first->len);
	  }
	quick_insert(first->data, last_point);
     }
   switch_to_buffer(save_buffer);
   pop_spot();
   return(1);
}

int copy_to_pastebuffer()
{
   /* delete paste buffer */
   if (Paste_Buffer != NULL) delete_buffer(Paste_Buffer);
   Paste_Buffer = make_buffer();
   strcpy(Paste_Buffer->name, " <paste>");

   copy_region_to_buffer(Paste_Buffer);
   return(0);
}

int delete_region (void)
{
   int beg_point, tmpm, end_point, n;
   Line *beg, *end;
   
   CHECK_READ_ONLY
    if (!check_region(&Number_Zero)) return(0);

   /* make this go through standard ins/del routines to ensure undo */
   
   end = CLine; end_point = Point;
   push_spot();
   tmpm = 1; pop_mark(&tmpm);
   beg = CLine; beg_point = Point;
   pop_spot();
   
   Point = 0;
   
   if (end != beg) 
     {
	deln(&end_point);
	
	/* go back because we do not want to mess with Line structures
	   changing on us --- shouldn't happen anyway */
	
	while (CLine = CLine->prev, LineNum--, CLine != beg)
	  {
	     eol(); 
	     n = Point + 1;
	     Point = 0;
	     generic_deln(&n);
	  }
	eol();
	n = Point - beg_point + 1;  /* the \n char */
	Point = beg_point;
     }
   else 
     {
	Point = beg_point;
	n = end_point - Point;
     }
   
   generic_deln(&n);
   return(1);
}

int kill_region()
{
   int tmpm = 1;
   
   CHECK_READ_ONLY

   /* need two marks for this one */
   push_spot();
   if (!pop_mark(&tmpm))
     {
	check_region(&Number_Zero);
	pop_spot();
	return(0);
     }
   push_mark();
   push_mark();
   pop_spot();

   copy_to_pastebuffer();
   delete_region();
   return(1);
}

static char *Rect_Error = "Rectangle has 0 width.";
int insert_rectangle()
{
   int c1;
   Line *rline;

   CHECK_READ_ONLY
   if (Rectangle_Buffer == NULL) return(0);

   Suspend_Screen_Update = 1;
   c1 = calculate_column();
   rline = Rectangle_Buffer->beg;
   if (rline != NULL) while (1)
     {
	goto_column(&c1);
	quick_insert(rline->data, rline->len);
	rline = rline->next;
	if (rline == NULL) break;
	if (CLine->next == NULL)
	  {
	     eol();
	     newline();
	  }
	else 
	  {
	     CLine = CLine->next;
	     LineNum++;
	  }
     }
   return(1);
}

int open_rectangle()
{
   int c1, n, c2, tmpm;
   Line *save_line;
   
   CHECK_READ_ONLY
   if (!check_region(&Number_One)) return(0); /* push_spot(); performed */

   c1 = calculate_column();
   save_line = CLine;
   tmpm = 1; pop_mark(&tmpm);
   c2 = calculate_column();
   n = c2 - c1;
   if (n < 0)
     {
	n = -n;
	c1 = c2;
     }
   
   Suspend_Screen_Update = 1;
   while(1)
     {
	goto_column(&c1);
	ins_char_n_times(' ', n);
	if (CLine == save_line) break;
	CLine = CLine->next;
	LineNum++;
     }
   pop_spot();

   return(1);
}

/* rectangle commands */
int copy_rectangle()
{
    Line *save_line, *line, *beg;
    int c1, c2, dc, tmp, tmpm, dc_malloc;
    unsigned char *p1, *p2, *data;
   
    if (!check_region(&Number_One)) return(0);       /* spot pushed */
    /* delete Rectangle buffer */
    if (Rectangle_Buffer != NULL) delete_buffer(Rectangle_Buffer);

    Rectangle_Buffer = make_buffer();
    strcpy(Rectangle_Buffer->name, " <rect>");
    c2 = calculate_column();
    save_line = CLine;

    tmpm = 1; pop_mark(&tmpm);
    c1 = calculate_column();
    if (c1 == c2)
      {
	 msg_error(Rect_Error);
	 pop_spot();
	 return(0);
      }
    if (c1 > c2)
      {
	 tmp = c1;
	 c1 = c2;
	 c2 = tmp;
	 goto_column(&c1);
      }

    /* go through the region copying rectanglar blocks to Rectanglebuffer */
    dc = c2 - c1;
   if (dc == 1) dc_malloc = 2; else dc_malloc = dc;
    line = beg = make_line1(dc_malloc);
   
    beg->prev = NULL;
    while (1)
      {
	 data = line->data;
	 /* p1 = data;
	 p2 = data + dc;
	 while (p1 < p2) *p1++ = ' '; */
	 MEMSET ((char *) data, ' ', dc);
	 
	 line->len = dc;

	 if (c1 == goto_column1(&c1))
	   {
	      p1 = CLine->data + Point;
	      (void) goto_column1(&c2);
	      p2 = CLine->data + Point;

	      /* while(p1 < p2) *data++ = *p1++; */
	      MEMCPY((char *) data, (char *) p1, (int) (p2 - p1));
	   }
	 if (CLine == save_line) break;
	 CLine = CLine->next;
	 LineNum++;

	 line->next = make_line1(dc_malloc);
	 line->next->prev = line;
	 line = line->next;
      }

    line->next = NULL;

    Rectangle_Buffer->line = Rectangle_Buffer->beg = beg;
    Rectangle_Buffer->end = line;
    Rectangle_Buffer->point = 0;

    pop_spot();
    return(0);
}

int kill_rectangle()
{
    Line *save_line, *line, *beg;
   int c1, c2, dc, tmp, n, tmpm, dc_malloc;
   unsigned char *p1, *p2, *data;

   CHECK_READ_ONLY
   if (!check_region(&Number_One)) return(0);

   /* delete Rectangle buffer */
   if (Rectangle_Buffer != NULL) delete_buffer(Rectangle_Buffer);

   Rectangle_Buffer = make_buffer();
   strcpy(Rectangle_Buffer->name, " <rect>");
   c2 = calculate_column();
   save_line = CLine;

   tmpm = 1; pop_mark(&tmpm);
    c1 = calculate_column();
    if (c1 == c2)
      {
	 msg_error(Rect_Error);
	 pop_spot();
	 return(0);
      }
    if (c1 > c2)
      {
	 tmp = c1;
	 c1 = c2;
	 c2 = tmp;
	 goto_column(&c1);
      }

   Suspend_Screen_Update = 1;
    /* go through the region copying rectanglar blocks to Rectanglebuffer */
    dc = c2 - c1;
   if (dc == 1) dc_malloc = 2; else dc_malloc = dc;
   		       /* length of 1 not usable here.
			* See makeline1 to see why 
			*/
    line = beg = make_line1(dc_malloc);
    beg->prev = NULL;
    while (1)
      {
	 data = line->data;
	 
	 /* p2 = data + dc; p1 = data;
	 while (p1 < p2) *p1++ = ' '; */
	 MEMSET((char *) data, ' ', dc);
	 line->len = dc;

	 if (c1 == goto_column1(&c1))
	   {
	      p1 = CLine->data + Point;
	      (void) goto_column1(&c2);
	      p2 = CLine->data + Point;
	      Point = (int) (p1 - CLine->data);
	      n = (int) (p2 - p1);
	      MEMCPY((char *) data, (char *) p1, n);
	      deln (&n);
	      /* while(n-- > 0)
	        {
		   *data++ = *p1;
		   del();
		} */
	   }
	 if (CLine == save_line) break;
	 CLine = CLine->next;  LineNum++;

	 line->next = make_line1(dc_malloc);
	 line->next->prev = line;
	 line = line->next;
      }

    line->next = NULL;

    Rectangle_Buffer->line = Rectangle_Buffer->beg = beg;
    Rectangle_Buffer->end = line;
    Rectangle_Buffer->point = 0;

    pop_spot();
    return(0);
}

int blank_rectangle()
{
   int c1, n, c2, pnt, tmpm;
   Line *save_line;
   int nn;
   

   CHECK_READ_ONLY
   if (!check_region(&Number_One)) return(0); /* push_spot(); performed */

   c1 = calculate_column();
   save_line = CLine;
   tmpm = 1; pop_mark(&tmpm);
   c2 = calculate_column();
   n = c2 - c1;
   if (n < 0)
     {
	n = -n;
	c1 = c2;
     }
   
   Suspend_Screen_Update = 1;
   while(1)
     {
	goto_column(&c1);
	pnt = Point;
	eol();
	nn = Point - pnt;
	if (nn > n) nn = n;
	Point = pnt;
	
	deln(&nn);
	ins_char_n_times( ' ', nn);
	
	if (CLine == save_line) break;
	CLine = CLine->next;
	LineNum++;
     }
   pop_spot();
   /* mark_buffer_modified(&Number_One); */
   return(1);
}

/*  User Marks */

typedef struct 
{
   Mark m;			       /* MUST be the first */
   Buffer *b;
}
User_Mark_Type;

static void free_user_mark (User_Mark_Type *um)
{
   Mark *m, *m1;
   Buffer *b;

   m1 = &um->m;

   /* The mark is only valid if the buffer that it was created for still
    * exists.
    */
   if ((m1->flags & MARK_INVALID) == 0)
     {
	/* Unlink the mark from the chain. */
	b = um->b;
	m = b->user_marks;
   
	if (m == m1)	b->user_marks = m1->next;
	else 
	  {
	     while (m->next != m1) m = m->next;
	     m->next = m1->next;
	  }
     }
   
   SLFREE (um);
}

void free_user_marks (Buffer *b)
{
   Mark *m = b->user_marks;
   
   while (m != NULL)
     {
	m->flags |= MARK_INVALID;
	m = m->next;
     }
}

static int mark_valid (Mark *m)
{
   if (m->flags & MARK_INVALID)
     {
	msg_error ("Mark is invalid.");
	return 0;
     }
   return 1;
}

int jed_move_user_object_mark (SLuser_Object_Type *uo)
{
   User_Mark_Type *um;
   Mark *m;
   
   um = (User_Mark_Type *) uo->obj;
   m = &um->m;
   
   if (!mark_valid (m)) return 0;
   
   if (CBuf != um->b)
     {	
	msg_error ("Mark not in buffer.");
	return 0;
     }
   
   
   m->line = CLine;
   m->point = Point;
   m->n = LineNum + CBuf->nup;
   return 1;
}

void move_user_mark (void)
{
   SLuser_Object_Type *uo;
   
   if ((uo = SLang_pop_user_object (JED_MARK_TYPE)) == NULL) return;
   (void) jed_move_user_object_mark (uo);
   SLang_free_user_object (uo);
}

 

void goto_user_mark (void)
{
   SLuser_Object_Type *uo;
   User_Mark_Type *um;
   
   if ((uo = SLang_pop_user_object (JED_MARK_TYPE)) == NULL) return;
   um = (User_Mark_Type *) uo->obj;
   
   if (mark_valid (&um->m))
     {
	if (CBuf != um->b) msg_error ("Mark not in buffer.");
	else
	  goto_mark (&um->m);
     }
   SLang_free_user_object (uo);
}

SLuser_Object_Type *jed_make_user_object_mark (int main_type)
{
   User_Mark_Type *um;
   SLuser_Object_Type *uo;
   Mark *m;
   
   uo = SLang_create_user_object (JED_MARK_TYPE);
   if (uo == NULL) return NULL;
   uo->main_type = main_type;
   
   if (NULL == (um = (User_Mark_Type *) SLMALLOC (sizeof(User_Mark_Type))))
     {
	SLang_Error = SL_MALLOC_ERROR;
	SLFREE (uo);
	return NULL;
     }
   m = &um->m;
   
   m->line = CLine;
   m->point = Point;
   m->n = LineNum + CBuf->nup;
   m->next = CBuf->user_marks;
   m->flags = 0;
   
   CBuf->user_marks = m;
   
   um->b = CBuf;   
   uo->obj = (long *) um;
   return uo;
}


void jed_free_user_object_mark (SLuser_Object_Type *uo)
{
   /* let slang handle this. */
   uo->main_type = SLANG_DATA;
   SLang_free_user_object (uo);
}

void create_user_mark (void)
{
   SLuser_Object_Type *uo = jed_make_user_object_mark (SLANG_DATA);
   if (uo != NULL) SLang_push_user_object (uo);
}

   
   
char *user_mark_buffer (void)
{
   SLuser_Object_Type *uo;
   User_Mark_Type *um;
   char *s = "";
   
   if (NULL == (uo = SLang_pop_user_object (JED_MARK_TYPE))) return s;
   um = (User_Mark_Type *) uo->obj;
   
   if (mark_valid (&um->m))
     {
	s = um->b->name;
     }
   
   SLang_free_user_object (uo);
   return s;
}

   
int register_jed_classes (void)
{
   return SLang_register_class (JED_MARK_TYPE, (FVOID_STAR) free_user_mark, NULL);
}

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