ftp.nice.ch/pub/next/tools/frontends/Emacs.3.0.1.s.tar.gz#/Emacs-3.0.1/EtermView.m

This is EtermView.m in view mode; [Download] [Up]

/* The EtermView implementation.

   For legal stuff see the file COPYRIGHT.  */

#import <dpsclient/dpsclient.h>
#import <mach/mach.h>
#import <libc.h>
#import <stdio.h>
#import <stdlib.h>
#import <sys/ioctl.h>
#import <sys/param.h>
#import <sys/time.h>

#import "EditListener.h"
#import "EtermView.h"
#import "EmacsApp.h"
#import "etermSupport.h"
#import "defaults.h"
#import "display.h"

extern char **environ;

/* These are the window size limits imposed by GNUEmacs.  */
#define MINWIDTH 10
#define MINHEIGHT 5
#define MAXWIDTH 300
#define MAXHEIGHT 300

/* Function prototypes for display.pswm */
void *new_display_rock (EtermView *theView);
void input_from_emacs (int fd, void *theDisplay);

/* Function prototypes for keycode.c */
void kc_init (void);
int kc_keyforcode (int code, int flags);

/* write(2), except it will keep retrying upon partial success.  */
static int
write_safely (int fd, char *buf, int n)
{
  int i, start = 0;

  while (start < n)
    {
      i = write (fd, buf + start, n - start);
      if (i == -1)
	return i; /* Some error */
      start += i;
    }
  return n;
}

/* Handle a connection to the event server socket */
static void
connection_from_emacs (int fd, void *rock)
{
  EtermView *view = (EtermView *) rock;
  int newfd = accept_server_connection (fd);

  if (newfd == -1)
    return;

  if ([view eventChannel])
    close (newfd);
  else
    [view setEventChannel: fdopen (newfd, "w")];
}

@implementation EtermView

+ initialize
{
  static NXDefaultsVector etermDefaults =
    {
      {"NXAutoLaunch", "NO" },
      {"NXFixedPitchFontSpacing", "0.95" },
      {"HideOnAutoLaunch", "NO" },
      {"EmacsPath", DEFAULT_EMACS_PATH},
      {"LispPath", DEFAULT_LISP_PATH},
      {NULL},
    };
  const char *sTypes[2], *rTypes[2];

  sTypes[0] = NXAsciiPboardType;
  sTypes[1] = NULL;
  rTypes[0] = NXAsciiPboardType;
  rTypes[1] = NULL;

  NXRegisterDefaults ([NXApp appName], etermDefaults);
  [NXApp registerServicesMenuSendTypes: sTypes andReturnTypes: rTypes];
  kc_init ();

  return self;
} /* +initialize */

-initFrame: (const NXRect *) newFrame
{
  self = [super initFrame: newFrame];
  [self setFlipped: YES];
#if 0
  [self notifyAncestorWhenFrameChanged: YES];
#endif

  spacing = atof (NXGetDefaultValue ([NXApp appName],
				     "NXFixedPitchFontSpacing"));
  /* Check for sane values of spacing.  */
  if (spacing < 0.9)
    spacing = 0.9;
  else if (spacing > 2.0)
    spacing = 2.0;
  [self setFont: [self font]];
  [[NXApp fontManager] setSelFont: [self font] isMultiple: NO];
  return self;
} /* -initFrame: */

-(float) spacing
{
  return spacing;
}

-setSpacing: (float) newSpacing
{
  char val[20];

  spacing = newSpacing;
  [self setFont: [self font]];
  sprintf (val, "%f", newSpacing);
  NXWriteDefault ([NXApp appName], "NXFixedPitchFontSpacing", val);
  return self;
}

-font
{
  return [Font userFixedPitchFontOfSize: (float) 0 matrix: NX_FLIPPEDMATRIX];
} /* -font */

-setFont: newFont
{
  NXCoord ascender, descender, lineHeight;
  NXRect frameRect, contentRect;
  Font *screenfont;
  int leading;

  /* NXTextFontInfo is broken for screen fonts, so call it before converting
     the font to a screenfont.  */
  NXTextFontInfo (newFont , &ascender, &descender, &lineHeight);
  screenfont = [newFont screenFont];
  if (screenfont)
    newFont = screenfont;

  leading = (spacing - 1.0) * lineHeight;
  fontHeight = (int) (lineHeight * spacing + leading);
  fontWidth = (float) ([newFont getWidthOf : "  "]
		       - [newFont getWidthOf : " "] + 0.5);
  fontDescender = (int) (descender + leading);

  [newFont set];
  if (screenfont)
    set_font ();
  else
    fix_font (fontWidth);
  displayFont = newFont;

  /* Snap the window size to a 1 x 1 character grid.  */
  [window getFrame: &frameRect];
  [self windowWillResize: self toSize: &frameRect.size];
  [Window getContentRect: &contentRect forFrameRect: &frameRect
          style: [window style]];
  [window sizeWindow: contentRect.size.width : contentRect.size.height];
  [self windowDidResize: self];
  [self display];

  return self;
} /* -setFont: */

/* Sent by fontManager.  */
-changeFont: sender
{
  id selectedFont;

  /* Get the font selected in the FontPanel.  */
  selectedFont = [[NXApp fontManager]
		  convertFont: [[NXApp fontManager] selFont]];
  [self setFont: selectedFont];
  [Font setUserFixedPitchFont: selectedFont];
  return self;
} /* -changeFont: */

-(FILE *) eventChannel;
{
  return eventChannel;
} /* -eventChannel */

-setEventChannel: (FILE *) fp;
{
  eventChannel = fp;
  return self;
} /* -setEventChannel: */

/* Constrain window so that the content view has an integral width in terms
   of characters.  XXX this method needs some cleaning up to stop the window
   from flashing while resizing.  */
-windowWillResize: sender toSize: (NXSize *) frameSize;
{
  static int oldw = 0, oldh = 0;
  int neww, newh;
  NXRect frameRect, contentRect;
  int newwidth, newheight;
  int style = [window style];

  NXSetRect (&frameRect, 0.0, 0.0, frameSize->width, frameSize->height);
  [Window getContentRect: &contentRect forFrameRect: &frameRect style: style];

  newwidth = (int) (contentRect.size.width + fontWidth / 2 - BORDER_WIDTH * 2);
  newheight = (int) (contentRect.size.height
		     + fontHeight / 2 - BORDER_WIDTH * 2);

  newwidth -= newwidth % fontWidth;
  newheight -= newheight % fontHeight;

  if (newwidth < MINWIDTH * fontWidth)
    newwidth = MINWIDTH * fontWidth;
  if (newwidth > MAXWIDTH * fontWidth)
    newwidth = MAXWIDTH * fontWidth;

  if (newheight < MINHEIGHT * fontHeight)
    newheight = MINHEIGHT * fontHeight;
  if (newheight > MAXHEIGHT * fontHeight)
    newheight = MAXHEIGHT * fontHeight;

  contentRect.size.width = (NXCoord) newwidth + BORDER_WIDTH * 2;
  contentRect.size.height = (NXCoord) newheight + BORDER_WIDTH * 2;
  [Window getFrameRect:&frameRect forContentRect:&contentRect style:style];

  frameSize->width = frameRect.size.width;
  frameSize->height = frameRect.size.height;

  newh = newheight / fontHeight;
  neww = newwidth / fontWidth;
  if (newh != oldh || neww != oldw)
    {
      [self showTitle: newh : neww];
      oldh = newh;
      oldw = neww;
    }
  return self;
} /* -windowWillResize:toSize: */

/* Convert size to number of characters in content view.  Inform child emacs
   process of our new size.  */
-windowDidResize: sender
{
  NXRect frameRect, contentRect;
  int style = [window style];
  struct winsize winsize;

  [window getFrame: &frameRect];
  [Window getContentRect: &contentRect forFrameRect: &frameRect style: style];

  lines = (int) ((contentRect.size.height - BORDER_WIDTH * 2) / fontHeight);
  cols =  (int) ((contentRect.size.width - BORDER_WIDTH * 2) / fontWidth);
  [self showTitle: lines : cols];

  /* emacs doesn't do anything with a TIOCCSWINSZ if LINES nor COLS change,
     so send a redraw screen in that case.  */
  winsize.ws_row = lines;
  winsize.ws_col = cols;
  winsize.ws_xpixel = (int) contentRect.size.width;
  winsize.ws_ypixel = (int) contentRect.size.height;
  ioctl (masterChannel, TIOCSWINSZ, &winsize);
  if (eventChannel)
    fprintf (eventChannel, "(redraw-display)\n");
  return self;
} /* -windowDidResize: */

/* Start up the child emacs process, perhaps on a file.  */
-startEmacs
{
  int master, slave, ptynumber, portno, i, n;
  char **args, **env;
  char *path;
  const char *lisp;

  /* Grab a pty/tty pair */
  create_channel (&master, &slave, &ptynumber);
  masterChannel = master;

  /* Create the server socket */
  eventServerSocket = create_server_socket (&portno);
  DPSAddFD (eventServerSocket, connection_from_emacs,
	    (void *) self, NX_RUNMODALTHRESHOLD);

  /* Frob the environment */
  env = patch_env (environ, portno);

  /* Make sure the size is set correctly */
  [self windowDidResize: self];

  args = calloc (startNum + 6, sizeof (char *));
  args[0] = "emacs";

  path = malloc (MAXPATHLEN + 1);
  lisp = NXGetDefaultValue ([NXApp appName], "LispPath");
  args[1] = "-l";
  args[3] = "-f";
  args[4] = "start-event-server";
  n = 5;
  if (strrchr (lisp, '/'))
    args[2] = (char *)lisp;
  else
    {
      if ([[NXBundle mainBundle] getPath: path forResource: lisp ofType: NULL])
	args[2] = path;
      else
	n = 1;
    }

  for (i=0 ; i<startNum ; i++) {
  	args[n++] = startFiles[i];
  }

  /* Fork off the Emacs */
  fork_shell ((char *)NXGetDefaultValue ([NXApp appName], "EmacsPath"),
	      args, env, slave);
  free (path);
  free (args);
  close (slave);

  rock = new_display_rock(self);
  DPSAddFD (master, input_from_emacs, rock, NX_RUNMODALTHRESHOLD);
  [window display];
  return self;
} /* -startEmacs: */

-(BOOL) acceptsFirstResponder
{
  return YES;
} /* -acceptsFirstResponder */

/* Keyboard input is given to the child emacs process.  XXX this method needs some cleeaning up.  */
-keyDown: (NXEvent *) theEvent;
{
  NXEvent eventbuf;
  char buf[1024];
  int i = 0;
  int code;

  if (theEvent->flags & NX_NUMERICPADMASK
      && theEvent->data.key.charCode >= 0xac
      && theEvent->data.key.charCode <= 0xaf)
    {
      /* Arrow keys (left, up, right, down) */
      if (theEvent->flags & NX_SHIFTMASK)
	buf[i++] = "\302\326\306\026"[theEvent->data.key.charCode - 0xac];
      else if (theEvent->flags & NX_CONTROLMASK)
	buf[i++] = "\001\274\005\276"[theEvent->data.key.charCode - 0xac];
      else if (theEvent->flags & NX_ALTERNATEMASK)
	buf[i++] = "\202\220\206\216"[theEvent->data.key.charCode - 0xac];
      else
	buf[i++] = "\002\020\006\016"[theEvent->data.key.charCode - 0xac];
    }
  else if (theEvent->flags & NX_NUMERICPADMASK
	   && theEvent->data.key.charCode == 0x03)
    {
      /* Enter key */
      buf[i++] = '\r';
    }
  else if (theEvent->flags & NX_ALTERNATEMASK)
    {
      /* Handle ALT key as a META key */
      code = kc_keyforcode(theEvent->data.key.keyCode, theEvent->flags);
      if (code != -1) {
	buf[i++] = code | 0x80;
      }
    }
  else buf[i++] = theEvent->data.key.charCode;

  /* Grab as many keypressed events as we can from a modal event loop */
  while (i < sizeof (buf)
	 && (theEvent = [NXApp peekNextEvent: NX_ALLEVENTS into: &eventbuf])
	 && theEvent->type == NX_KEYDOWN
	 && !(theEvent->flags & NX_COMMANDMASK))
    {
      theEvent = [NXApp getNextEvent: NX_ALLEVENTS];

      if (theEvent->flags & NX_NUMERICPADMASK
	  && theEvent->data.key.charCode >= 0xac
	  && theEvent->data.key.charCode <= 0xaf)
	{
	  /* Arrow keys (left, up, right, down) */
	  if (theEvent->flags & NX_SHIFTMASK)
	    buf[i++] = "\302\326\306\026"[theEvent->data.key.charCode - 0xac];
	  else if (theEvent->flags & NX_CONTROLMASK)
	    buf[i++] = "\001\274\005\276"[theEvent->data.key.charCode - 0xac];
	  else if (theEvent->flags & NX_ALTERNATEMASK)
	    buf[i++] = "\202\220\206\216"[theEvent->data.key.charCode - 0xac];
	  else
	    buf[i++] = "\002\020\006\016"[theEvent->data.key.charCode - 0xac];
	}
      else if (theEvent->flags & NX_NUMERICPADMASK
	       && theEvent->data.key.charCode == 0x03)
	{
	  /* Enter key */
	  buf[i++] = '\r';
	}
      else if (theEvent->flags & NX_ALTERNATEMASK)
	{
	  /* Handle ALT key as a META key */
	  code = kc_keyforcode (theEvent->data.key.keyCode, theEvent->flags);
	  if (code != -1)
	    buf[i++] = code | 0x80;
	}
      else buf[i++] = theEvent->data.key.charCode;
    }

  write_safely (masterChannel, buf, i);
  obscure_cursor ();

  return self;
} /* -keyDown: */

/* Mouse input is converted to a character position and given to the child
   emacs process, but only if the emacs has opened an event port.  */
-mouseEvent: (NXEvent *) theEvent : (int) button;
{
  int x, y;
  char buf[35];

  if (!eventChannel)
    return self;

  [self convertPoint: &theEvent->location fromView:nil];
  x = (int) ((theEvent->location.x - BORDER_WIDTH) / fontWidth);
  y = (int) ((theEvent->location.y - BORDER_WIDTH) / fontHeight);

  if (x < 0)
    x = 0;
  if (y < 0)
    y = 0;
  if (x >= cols)
    x = cols - 1;
  if (y >= lines)
    y = lines - 1;

  if (oldx != x || oldy != y) {

      sprintf (buf, "\030%c(%d %d %d 1)\r", 0,
	       (button + ((theEvent->flags & NX_SHIFTMASK) ? 8 : 0)
		+ ((theEvent->flags & NX_CONTROLMASK) ? 16 : 0)
		+ ((theEvent->flags & NX_ALTERNATEMASK) ? 32 : 0)), x, y);
      write_safely (masterChannel, buf, 2+strlen (buf+2));

      oldx = x;
      oldy = y;
    }

  return self;
} /* -mouseEvent:: */


-mouseDown: (NXEvent *) theEvent;
{
  [self mouseEvent: theEvent: 1];
  oldmask = [window addToEventMask:NX_LMOUSEDRAGGEDMASK];
  return self;
} /* -mouseDown: */

-mouseUp: (NXEvent *) theEvent;
{
  oldmask = [window removeFromEventMask:NX_LMOUSEDRAGGEDMASK];
  [self mouseEvent: theEvent: 1 + 128];
  return self;
} /* -mouseUp */

-mouseDragged:  (NXEvent *) theEvent;
{
  NXEvent	*foundEvent = theEvent, *searchEvent;
  int		filterCount = 0;

  while ((++filterCount < 10) &&
	 ((searchEvent = [NXApp getNextEvent:NX_LMOUSEDRAGGEDMASK waitFor:0.034
			  threshold:NX_MODALRESPTHRESHOLD]) != NULL))
      foundEvent = searchEvent;

  [self mouseEvent: foundEvent: 1 + 128];
  return self;
} /* -mouseDragged */

-rightMouseDown: (NXEvent *) theEvent;
{
  [self mouseEvent: theEvent: 4];
  return self;
} /* rightMouseDown: */

-rightMouseUp: (NXEvent *) theEvent;
{
  [self mouseEvent: theEvent: 4 + 128];
  return self;
} /* rightMouseUp: */

-(BOOL) emacsOn
{
  if (eventChannel) {
    if (startNum > 0) {
      int index;

      /* Now that we have a connection established, make sure all the open
         requests that have line numbers are pointed at the right place.   */
      for (index = 0 ; index < startNum ; index++) {
	if (startLines[index] >= 0)
	  [self openFile:startFiles[index] onHost:"" 
		atTrueLine:startLines[index]];
	else
	  [self newFile:startFiles[index]];
      }
      startNum = 0;
    }
    return YES;
  }
  else
    return NO;
} /* -emacsOn */

-(BOOL) newFile: (const char *) path
{
  if (!eventChannel)
    return [self addFile:path atLine:-1];

  fprintf (eventChannel, "(find-file \"%s\")\n", path);
  fflush (eventChannel);
  return YES;
} /* -newFile: */
    
/* Quit, cut, copy, paste, and undo messages are sent to the child's event
   port.  */
-quitEmacs
{
  if (!eventChannel)
    return nil;
  fprintf (eventChannel, "(event-quit)\n");
  fflush (eventChannel);
  return self;
} /* -quitEmacs */

-pasteboard
{
  if (!currentPasteboard)
    currentPasteboard = [self appPasteboard];
  return currentPasteboard;
} /* -pasteboard */

-appPasteboard
{
  if (!mainPasteboard)
    mainPasteboard = [Pasteboard new];
  return mainPasteboard;
} /* -appPasteboard */

-setPasteboard: pboard
{
  if (pboard != nil)
    currentPasteboard = pboard;
  return self;
} /* -setPasteboard: */

-(BOOL) sendEmacsEvent: (char *) theEvent
{
  if (!eventChannel)
    return NO;
  fprintf (eventChannel, "%s\n", theEvent);
  fflush (eventChannel);
  return YES;
} /* -(BOOL) sendEmacsEvent: */

-pasteboardWritten
{
  pasteboardWaiting = NO;
  return self;
}

-(BOOL)pasteboardWaiting
{
  return pasteboardWaiting;
}


-undo: sender;
{
  [self sendEmacsEvent: "(undo)"];
  return self;
} /* -undo: */

-save: sender;
{
  [self sendEmacsEvent: "(save-buffer)"];
  return self;
} /* -save: */

-saveAll: sender;
{
  [self sendEmacsEvent: "(save-some-buffers t)"];
  return self;
} /* -saveAll: */

-cut: sender;
{
  [self cutTo: [self appPasteboard]];
  return self;
} /* -cut: */

-copy: sender;
{
  [self copyTo: [self appPasteboard]];
  return self;
} /* -copy: */

-paste: sender;
{
  [self pasteFrom: [self appPasteboard]];
  return self;
} /* -paste: */

-open: sender;
{
  int i;
  char *path;
  const char *directory;
  const char *const *fileNames;

  if (!eventChannel)
    return self;
  if (!openPanel)
    openPanel = [OpenPanel new];
  [openPanel allowMultipleFiles: YES];
  [openPanel chooseDirectories: NO];
  if ([openPanel runModal])
    {
      directory = [openPanel directory];
      fileNames = [openPanel filenames];
      if (fileNames)
	{
	  for (i = 0; fileNames[i]; i++)
	    {
	      path = malloc (2 + strlen (directory) + strlen (fileNames[i]));
	      strcat (strcat (strcpy (path, directory), "/"), fileNames[i]);
	      fprintf (eventChannel, "(find-file \"%s\")\n", path);
	      fflush (eventChannel);
	      free (path);
	    }
	}
    }
  return self;
} /* -open: */

-saveAs: sender;
{
  const char *path;

  if (!eventChannel)
    return self;
  if (!savePanel)
    savePanel = [SavePanel new];
  if ([savePanel runModal])
    {
      path = [savePanel filename];
      if (path)
	{
	  fprintf (eventChannel, "(write-file \"%s\")\n", path);
	  fflush (eventChannel);
	}
    }
  return self;
} /* -saveAs: */

-(BOOL) cutTo: pasteboard
{
  return [[self setPasteboard: pasteboard] sendEmacsEvent: "(event-cut)"];
} /* -(BOOL) cutTo: */

-(BOOL) copyTo: pasteboard
{
  return [[self setPasteboard: pasteboard] sendEmacsEvent: "(event-copy)"];
} /* -(BOOL) copyTo: */

-(BOOL) pasteFrom: pasteboard
{
  const NXAtom *types, *p;
  char *data;
  int len;

  if (!eventChannel || !pasteboard)
    return NO;
  types = [pasteboard types];
  for (p = types; *p && strcmp (*p, NXAsciiPboardType); p++);

  if (*p && [pasteboard readType: NXAsciiPboardType data: &data length: &len]
      && len)
    {
      int c;
      char *tosend = data;

      fputs("(event-paste \"", eventChannel);

      /* Write out string, quoting quote, backslash, and newline */
      for (tosend = data; len--; tosend++)
	{
	  switch (c = *tosend)
	    {
	    case '\n':
	      fputs ("\\n", eventChannel);
	      break;
	    case '\\':
	    case '\"':
	      putc('\\', eventChannel);
	      /* FALL THROUGH */
	    default:
	      putc (c, eventChannel);
	      break;
	    }
	}
      fputs ("\")\n", eventChannel);
      fflush (eventChannel);
      vm_deallocate (task_self (), (vm_address_t) data, len);
    }
  return YES;
} /* -(BOOL) pasteFrom: */

-getDimensions: (int *) linesPtr : (int *) colsPtr
{
  *linesPtr = lines;
  *colsPtr = cols;
  return self;
} /* -getDimensions:: */

-(Font *) getDisplayFont:
  (int *) heightPtr :
  (int *) widthPtr :
  (int *) descenderPtr
{
  *heightPtr = fontHeight;
  *widthPtr = fontWidth;
  *descenderPtr = fontDescender;
  return displayFont;
} /* -getDisplayFont::: */

-showTitle: (int) newLines : (int) newColumns
{
  char *newtitle;

  if (!titleprefix)
    return self;
  newtitle = malloc (22 + strlen (titleprefix));
  sprintf (newtitle, "%s (%dx%d)", titleprefix, newColumns, newLines);
  [(Window *) window setTitle: newtitle];
  free (newtitle);
  return self;
} /* -showTitle:: */

-setTitle: (char *) title
{
  titleprefix = title;
  [self showTitle: lines : cols];
  return self;
} /* -setTitle: */

-validRequestorForSendType: (NXAtom) typeSent
 andReturnType: (NXAtom) typeReturned
{
  /* Check to make sure that the types are ones that we can handle.  */
  if (eventChannel && (typeSent == NXAsciiPboardType || typeSent == NULL)
      && (typeReturned == NXAsciiPboardType || typeReturned == NULL))
    return self;
  /* Otherwise, return the default.  */
  return [super validRequestorForSendType: typeSent
                andReturnType: typeReturned];
} /* -validRequestorForSendType:andReturnType: */

-(BOOL) writeSelectionToPasteboard: pboard types: (NXAtom *) types
{
  const NXAtom	*p;
  BOOL			rightType = NO;

  if (eventChannel && pboard && rock)
    {
      p = types;
      while (*p) {
	if (!strcmp(*p, NXAsciiPboardType)) {
	  rightType = YES;
	  break;
	}
	p++;
      }

      /* Ok, so this is slightly sick.  We loop until emacs receives and
         processes the event.  Then we know we've completely dealt with the
         pasteboard.  We can't return until something has been pasted to it.
         Or the application will probably end up choking.  We put in a
         timeout to prevent hanging.					   */
      if (rightType) {
	struct timeval	theTime;
	struct timezone	dummy;
	long		curTime, elapsedTime;

	gettimeofday(&theTime, &dummy);
	curTime = theTime.tv_sec + theTime.tv_usec / 1000000;
	elapsedTime = 0;
	pasteboardWaiting = YES;
	if (![self copyTo: pboard])
	  return NO;
	while (pasteboardWaiting &&
	       (elapsedTime < 5)) {
	  input_from_emacs(masterChannel, rock);
	  gettimeofday(&theTime, &dummy);
	  elapsedTime = (theTime.tv_sec + theTime.tv_usec / 1000000) - curTime;
	}
	if (pasteboardWaiting) {
	  NXBeep();
	  NXBeep();
	  return NO;
	}
	return YES;
      }
    }
  return NO;
} /* -writeSelectionToPasteboard:types: */

-readSelectionFromPasteboard: pboard
{
  [self sendEmacsEvent: "(call-interactively 'kill-region)"];
  [self pasteFrom: pboard];
  return self;
} /* -readSelectionFromPasteboard: */


  /* Opening up a file remotely and showing the right line number.	   */
-(int)openFile : (char *) fileName
	onHost : (char *) hostName
	atTrueLine : (int) line
{
  if (!eventChannel)
    return [self addFile:fileName atLine:line];

  fprintf(eventChannel, "(find-file \"%s\")\n", fileName);
  fprintf(eventChannel, "(goto-line %d)\n", line);
  fprintf(eventChannel, "(if (not overlay-arrow-position)");
  fprintf(eventChannel, "    (progn (setq overlay-arrow-position (point-marker)) ");
  fprintf(eventChannel, "           (setq overlay-arrow-string \"=>\")))\n");
  fprintf(eventChannel, "(set-marker overlay-arrow-position ");
  fprintf(eventChannel, "            (- (point) (current-column)))\n");
  fflush(eventChannel);
  return YES;
}

-(int)openFile : (char *) fileName
	onHost : (char *) hostName
	fromTrueLine : (int) line1
	to : (int) line2
{
  return [self openFile:fileName onHost:hostName atTrueLine:line1];
}

- (BOOL)addFile:(const char *)path atLine:(int)line
{
  if (startFiles == NULL) {
    startFiles = (char **)malloc((startNum + 1) * sizeof(char *));
    startLines = (int *)malloc((startNum + 1) * sizeof(int));
  }
  else {
    startFiles = (char **)realloc(startFiles, (startNum+1) * sizeof (char *));
    startLines = (int *)realloc(startLines, (startNum+1) * sizeof (int));
  }
  if ((startFiles == NULL) || (startLines == NULL)) return NO;

  startLines[startNum] = line;
  startFiles[startNum] = strcpy (malloc (1 + strlen (path)), path);
  startNum++;
  return YES;
}

@end

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