/* -*-C-*-

$Id: os2pmcon.c,v 1.31 2003/07/22 02:19:51 cph Exp $

Copyright 1994-2000 Massachusetts Institute of Technology

This file is part of MIT/GNU Scheme.

MIT/GNU Scheme 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 of the License, or (at
your option) any later version.

MIT/GNU Scheme 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 MIT/GNU Scheme; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.

*/

#define INCL_WIN
#include "os2.h"
#include "os2pmcon.h"

/* #define CONSOLE_WRAP */

static void grab_console_lock (void);
static void release_console_lock (void);
static unsigned short cx2x (unsigned short);
static unsigned short cy2y (unsigned short, int);
static unsigned short x2cx (short, int);
static unsigned short y2cy (short, int);
static void process_events (int);
static void initialize_marked_region (short, short);
static void update_marked_region (short, short);
static void unmark_marked_region (void);
static int marked_region_nonempty_p (void);
static char * extract_marked_region (int);
static void compute_marked_region
  (short, short, short, short,
   unsigned short *, unsigned short *, unsigned short *, unsigned short *);
static void highlight_marked_region
  (unsigned short, unsigned short, unsigned short, unsigned short, char);
static void paint_marked_region_segment
  (unsigned short, unsigned short, unsigned short, unsigned short);
static void disable_marked_region (void);
static void enable_menu_copy_items (int);
static void console_resize (unsigned short, unsigned short);
static void console_paint
  (unsigned short, unsigned short, unsigned short, unsigned short);
static unsigned short compute_run_length (const char *, const char *);
static void console_clear
  (unsigned short, unsigned short, unsigned short, unsigned short);
static void console_clear_all (void);
static int do_paste (void);
static int translate_key_event
  (MPARAM, MPARAM, unsigned short *, unsigned char *);
static const char * find_nonprint (const char *, const char *);
static void do_carriage_return (void);
static void do_linefeed (void);
static unsigned short find_invalid_line (unsigned short, unsigned short);
static void do_formfeed (void);
static void do_backspace (void);
static void do_alert (void);

static HMTX console_lock;
static unsigned short console_pel_width;
static unsigned short console_pel_height;
static unsigned short console_width;
static unsigned short console_height;
static char * console_chars;
static char * console_highlights;
static unsigned short * console_line_lengths;
static font_metrics_t * console_metrics;
static unsigned short point_x;
static unsigned short point_y;
static int console_visiblep;
static int console_closedp;
static unsigned short readahead_repeat;
static char readahead_char;
static const char * readahead_insert;
static const char * readahead_insert_scan;
static void * pending_events;
static tqueue_t * console_tqueue;
static qid_t console_event_qid;
static qid_t console_pm_qid;
static wid_t console_wid;
static psid_t console_psid;
static int console_tracking_mouse_p;
static HWND console_tracking_mouse_pointer;
static int console_marked_region_active_p;
static HWND console_menu;
static short console_mark_x;
static short console_mark_y;
static short console_point_x;
static short console_point_y;

static const char * console_font_specs [] =
  { "8.Courier", "10.Courier", "12.Courier", 
    "4.System VIO", "10.System Monospaced" };

#define CHAR_WIDTH (FONT_METRICS_WIDTH (console_metrics))
#define CHAR_HEIGHT (FONT_METRICS_HEIGHT (console_metrics))
#define CHAR_DESCENDER (FONT_METRICS_DESCENDER (console_metrics))
#define CHAR_LOC(x, y) (& (console_chars [((y) * console_width) + (x)]))
#define CHAR_HL(x, y) (& (console_highlights [((y) * console_width) + (x)]))
#define LINE_LEN_LOC(y) ((char *) (& (console_line_lengths [(y)])))

#define FASTFILL(p, n, c)						\
{									\
  char * FASTFILL_scan = (p);						\
  char * FASTFILL_end = (FASTFILL_scan + (n));				\
  while (FASTFILL_scan < FASTFILL_end)					\
    (*FASTFILL_scan++) = (c);						\
}

void
OS2_initialize_pm_console (void)
{
  console_lock = (OS2_create_mutex_semaphore (0, 0));
  console_pel_width = 0;
  console_pel_height = 0;
  console_width = 0;
  console_height = 0;
  console_chars = 0;
  console_highlights = 0;
  console_line_lengths = 0;
  point_x = 0;
  point_y = 0;
  console_visiblep = 0;
  console_closedp = 0;
  console_tracking_mouse_p = 0;
  console_tracking_mouse_pointer
    = (WinQuerySysPointer (HWND_DESKTOP, SPTR_TEXT, FALSE));
  readahead_repeat = 0;
  readahead_insert = 0;
  pending_events = (OS2_create_msg_fifo ());
  console_tqueue = (OS2_make_std_tqueue ());
  {
    qid_t remote;
    OS2_make_qid_pair ((&console_event_qid), (&remote));
    OS2_open_qid (console_event_qid, console_tqueue);
    console_pm_qid = (OS2_create_pm_qid (console_tqueue));
    console_wid
      = (OS2_window_open (console_pm_qid, remote,
			  (FCF_TITLEBAR | FCF_SYSMENU
			   | FCF_SHELLPOSITION | FCF_SIZEBORDER
			   | FCF_MINMAX | FCF_TASKLIST | FCF_NOBYTEALIGN
			   | FCF_MENU | FCF_ACCELTABLE | FCF_ICON),
			  NULLHANDLE,
			  ID_PMCON_RESOURCES,
			  0, "Scheme"));
  }
  OS2_window_permanent (console_wid);
  {
    psid_t psid = (OS2_window_client_ps (console_wid));
    const char ** scan_specs = console_font_specs;
    const char ** end_specs
      = (scan_specs
	 + ((sizeof (console_font_specs)) / (sizeof (const char *))));
    console_metrics = 0;
    while (scan_specs < end_specs)
      {
	const char * spec = (*scan_specs++);
	/* This prevents the font-change hook from being invoked.  */
	console_psid = 0;
	console_metrics = (OS2_ps_set_font (psid, 1, spec));
	if (console_metrics != 0)
	  break;
      }
    if (console_metrics == 0)
      OS2_logic_error ("Unable to find usable console font.");
    console_psid = psid;
  }
  OS2_window_set_grid (console_wid, CHAR_WIDTH, CHAR_HEIGHT);
  OS2_window_shape_cursor
    (console_wid, CHAR_WIDTH, CHAR_HEIGHT, (CURSOR_SOLID | CURSOR_FLASH));
  OS2_window_show_cursor (console_wid, 1);
  OS2_window_show (console_wid, 1);
  OS2_window_activate (console_wid);
  {
    unsigned short width;
    unsigned short height;
    unsigned short max_width = (80 * CHAR_WIDTH);
    OS2_window_size (console_wid, (& width), (& height));
    console_resize (width, height);
    if (width > max_width)
      OS2_window_set_size (console_wid, max_width, height);
  }
  console_menu
    = (OS2_window_handle_from_id (console_pm_qid,
				  (OS2_window_frame_handle (console_wid)),
				  FID_MENU));
  disable_marked_region ();
}

wid_t
OS2_console_wid (void)
{
  return (console_wid);
}

psid_t
OS2_console_psid (void)
{
  return (console_psid);
}

void
OS2_console_font_change_hook (font_metrics_t * metrics)
{
  font_metrics_t * copy = (OS_malloc (sizeof (font_metrics_t)));
  FASTCOPY (((char *) metrics), ((char *) copy), (sizeof (font_metrics_t)));
  grab_console_lock ();
  OS_free (console_metrics);
  console_metrics = copy;
  OS2_window_set_grid (console_wid, CHAR_WIDTH, CHAR_HEIGHT);
  OS2_window_shape_cursor
    (console_wid, CHAR_WIDTH, CHAR_HEIGHT, (CURSOR_SOLID | CURSOR_FLASH));
  console_resize (console_pel_width, console_pel_height);
  OS2_window_invalidate (console_wid,
			 0, console_pel_width,
			 0, console_pel_height);
  release_console_lock ();
}

static void
grab_console_lock (void)
{
  OS2_request_mutex_semaphore (console_lock);
}

static void
release_console_lock (void)
{
  OS2_release_mutex_semaphore (console_lock);
}

static unsigned short
cx2x (unsigned short x)
{
  return (x * CHAR_WIDTH);
}

static unsigned short
cy2y (unsigned short y, int lowerp)
{
  /* lowerp => result is bottommost pel of cell.  Otherwise result is
     bottommost pel of cell above.  */
  unsigned short limit = (lowerp ? (console_height - 1) : console_height);
  return ((y < limit) ? ((limit - y) * CHAR_HEIGHT) : 0);
}

static unsigned short
x2cx (short x, int lowerp)
{
  /* lowerp => `x' is inclusive lower bound, and result is cell it
     falls in.  Otherwise, `x' is exclusive upper bound, and result is
     cell to its right, unless it falls on leftmost edge of cell.  If
     the argument is inclusive-lower, then the result is also;
     likewise for exclusive-upper.  */
  short cx = (x / ((short) CHAR_WIDTH));
  if (! (lowerp || ((x % ((short) CHAR_WIDTH)) == 0)))
    cx += 1;
  return ((cx < 0) ? 0 : (cx > console_width) ? console_width : cx);
}

static unsigned short
y2cy (short y, int lowerp)
{
  /* lowerp => `y' is inclusive lower bound, and result is cell below
     the one it falls in.  Otherwise, `y' is exclusive upper bound,
     and result is cell it falls in, unless it falls on bottommost
     edge of cell, when result is cell below.  If the argument is
     inclusive-lower, then the result is exclusive-upper, and
     vice-versa.  */
  short cy = (((short) (console_height - 1)) - (y / ((short) CHAR_HEIGHT)));
  if (lowerp || ((y % ((short) CHAR_HEIGHT)) == 0))
    cy += 1;
  return ((cy < 0) ? 0 : (cy > console_height) ? console_height : cy);
}

static void
process_events (int blockp)
{
  while (1)
    {
      msg_t * message
	= (OS2_receive_message (console_event_qid, blockp, 0));
      if (message == 0)
	break;
      switch (MSG_TYPE (message))
	{
	case mt_paint_event:
	  {
	    unsigned short xl = (SM_PAINT_EVENT_XL (message));
	    unsigned short xh = (SM_PAINT_EVENT_XH (message));
	    unsigned short yl = (SM_PAINT_EVENT_YL (message));
	    unsigned short yh = (SM_PAINT_EVENT_YH (message));
	    OS2_destroy_message (message);
	    grab_console_lock ();
	    OS2_ps_clear (console_psid, xl, xh, yl, yh);
	    console_paint ((x2cx (xl, 1)),
			   (x2cx (xh, 0)),
			   (y2cy (yh, 0)),
			   (y2cy (yl, 1)));
	    release_console_lock ();
	    break;
	  }
	case mt_pm_event:
	  {
	    ULONG msg = (SM_PM_EVENT_MSG (message));
	    MPARAM mp1 = (SM_PM_EVENT_MP1 (message));
	    MPARAM mp2 = (SM_PM_EVENT_MP2 (message));
	    switch (msg)
	      {
	      case WM_CHAR:
	      case WM_CLOSE:
	      postpone_event:
		OS2_msg_fifo_insert (pending_events, message);
		message = 0;
		if (blockp)
		  return;
		break;
	      case WM_SIZE:
		{
		  unsigned short new_pel_width = (SHORT1FROMMP (mp2));
		  unsigned short new_pel_height = (SHORT2FROMMP (mp2));
		  grab_console_lock ();
		  console_resize (new_pel_width, new_pel_height);
		  release_console_lock ();
		  break;
		}
	      case WM_SHOW:
		if ((!console_visiblep) && (SHORT1FROMMP (mp1)))
		  {
		    grab_console_lock ();
		    OS2_window_invalidate (console_wid,
					   0, console_pel_width,
					   0, console_pel_height);
		    release_console_lock ();
		  }
		console_visiblep = (SHORT1FROMMP (mp1));
		break;
	      case WM_BUTTON1DOWN:
		grab_console_lock ();
		if (!OS2_window_focusp (console_wid))
		  OS2_window_activate (console_wid);
		else if (OS2_window_set_capture (console_wid, 1))
		  {
		    console_tracking_mouse_p = 1;
		    initialize_marked_region ((SHORT1FROMMP (mp1)),
					      (SHORT2FROMMP (mp1)));
		    OS2_window_mousetrack (console_wid, 1);
		    OS2_set_pointer (console_pm_qid,
				     HWND_DESKTOP,
				     console_tracking_mouse_pointer);
		  }
		else
		  (void) WinAlarm (HWND_DESKTOP, WA_ERROR);
		release_console_lock ();
		break;
	      case WM_BUTTON1UP:
		if (console_tracking_mouse_p)
		  {
		    grab_console_lock ();
		    update_marked_region ((SHORT1FROMMP (mp1)),
					  (SHORT2FROMMP (mp1)));
		    (void) OS2_window_set_capture (console_wid, 0);
		    OS2_window_mousetrack (console_wid, 0);
		    enable_menu_copy_items (marked_region_nonempty_p ());
		    console_tracking_mouse_p = 0;
		    release_console_lock ();
		  }
		break;
	      case WM_MOUSEMOVE:
		if (console_tracking_mouse_p)
		  {
		    grab_console_lock ();
		    update_marked_region ((SHORT1FROMMP (mp1)),
					  (SHORT2FROMMP (mp1)));
		    OS2_set_pointer (console_pm_qid,
				     HWND_DESKTOP,
				     console_tracking_mouse_pointer);
		    release_console_lock ();
		  }
		break;
	      case WM_BUTTON2DOWN:
	      case WM_BUTTON3DOWN:
		grab_console_lock ();
		if (!OS2_window_focusp (console_wid))
		  OS2_window_activate (console_wid);
		release_console_lock ();
		break;
	      case WM_COMMAND:
		switch (SHORT1FROMMP (mp1))
		  {
		  case IDM_CUT:
		  case IDM_COPY:
		  case IDM_PASTE:
		    goto postpone_event;
		  case IDM_FONT:
		    grab_console_lock ();
		    {
		      const char * font_spec
			= (OS2_window_font_dialog (console_wid,
						   "Console Window Font"));
		      if (font_spec != 0)
			{
			  (void) OS2_ps_set_font (console_psid, 1, font_spec);
			  OS_free ((void *) font_spec);
			}
		    }
		    release_console_lock ();
		    break;
		  case IDM_EXIT:
		    termination_normal (0);
		    break;
		  case IDM_ABOUT:
		    (void) WinMessageBox
		      (HWND_DESKTOP,
		       NULLHANDLE,
		       ("This is " PACKAGE_STRING),
		       PACKAGE_VERSION,
		       0,
		       MB_OK);
		    break;
		  }
	      }
	    if (message != 0)
	      OS2_destroy_message (message);
	  }
	  break;
	default:
	  OS2_destroy_message (message);
	  break;
	}
    }
}

static void
initialize_marked_region (short x, short y)
{
  unmark_marked_region ();
  console_mark_x = x;
  console_mark_y = y;
  console_point_x = x;
  console_point_y = y;
  console_marked_region_active_p = 1;
}

static void
update_marked_region (short x, short y)
{
  unsigned short cx11;
  unsigned short cy11;
  unsigned short cx21;
  unsigned short cy21;
  unsigned short cx12;
  unsigned short cy12;
  unsigned short cx22;
  unsigned short cy22;

  unsigned short i11;
  unsigned short i21;
  unsigned short i12;
  unsigned short i22;

  if (!console_marked_region_active_p)
    return;

  compute_marked_region (console_mark_x, console_mark_y,
			 console_point_x, console_point_y,
			 (&cx11), (&cy11), (&cx21), (&cy21));
  highlight_marked_region (cx11, cy11, cx21, cy21, '\0');

  compute_marked_region (console_mark_x, console_mark_y, x, y,
			 (&cx12), (&cy12), (&cx22), (&cy22));
  highlight_marked_region (cx12, cy12, cx22, cy22, '\1');

  i11 = ((cy11 * console_width) + cx11);
  i21 = ((cy21 * console_width) + cx21);
  i12 = ((cy12 * console_width) + cx12);
  i22 = ((cy22 * console_width) + cx22);

  if (i11 < i12)
    paint_marked_region_segment (cx11, cy11, cx12, cy12);
  else if (i12 < i11)
    paint_marked_region_segment (cx12, cy12, cx11, cy11);
  if (i21 < i22)
    paint_marked_region_segment (cx21, cy21, cx22, cy22);
  else if (i22 < i21)
    paint_marked_region_segment (cx22, cy22, cx21, cy21);

  console_point_x = x;
  console_point_y = y;
}

static void
unmark_marked_region (void)
{
  if (console_marked_region_active_p)
    {
      unsigned short cx1;
      unsigned short cy1;
      unsigned short cx2;
      unsigned short cy2;
      compute_marked_region (console_mark_x, console_mark_y,
			     console_point_x, console_point_y,
			     (&cx1), (&cy1), (&cx2), (&cy2));
      highlight_marked_region (cx1, cy1, cx2, cy2, '\0');
      paint_marked_region_segment (cx1, cy1, cx2, cy2);
      disable_marked_region ();
    }
}

static int
marked_region_nonempty_p (void)
{
  if (console_marked_region_active_p)
    {
      unsigned short cx1;
      unsigned short cy1;
      unsigned short cx2;
      unsigned short cy2;
      unsigned short y;
      compute_marked_region (console_mark_x, console_mark_y,
			     console_point_x, console_point_y,
			     (&cx1), (&cy1), (&cx2), (&cy2));
      return
	((cy1 < cy2)
	 || ((cx1 < cx2) && (cx1 < (console_line_lengths[cy1]))));
    }
  else
    return (0);
}

static char *
extract_marked_region (int cutp)
{
  if (console_marked_region_active_p)
    {
      unsigned short cx1;
      unsigned short cy1;
      unsigned short cx2;
      unsigned short cy2;
      unsigned short length;
      unsigned short y;
      char * result;
      char * scan;

      compute_marked_region (console_mark_x, console_mark_y,
			     console_point_x, console_point_y,
			     (&cx1), (&cy1), (&cx2), (&cy2));
      length = 1;
      for (y = cy1; (y <= cy2); y += 1)
	{
	  unsigned short xl = ((y == cy1) ? cx1 : 0);
	  unsigned short xh = ((y == cy2) ? cx2 : console_width);
	  unsigned short lx = (console_line_lengths[y]);
	  if (y > cy1)
	    length += 2;
	  if (xl < lx)
	    length += (((xh < lx) ? xh : lx) - xl);
	}
      if (length == 1)
	return (0);
      result = (OS_malloc (length));
      scan = result;
      for (y = cy1; (y <= cy2); y += 1)
	{
	  unsigned short xl = ((y == cy1) ? cx1 : 0);
	  unsigned short xh = ((y == cy2) ? cx2 : console_width);
	  unsigned short lx = (console_line_lengths[y]);
	  if (y > cy1)
	    {
	      (*scan++) = '\r';
	      (*scan++) = '\n';
	    }
	  if (xl < lx)
	    {
	      unsigned short ll = (((xh < lx) ? xh : lx) - xl);
	      FASTCOPY ((CHAR_LOC (xl, y)), scan, ll);
	      scan += ll;
	    }
	}
      (*scan) = '\0';
      if (cutp)
	{
	  unsigned short x1
	    = ((cx1 < (console_line_lengths[cy1]))
	       ? cx1
	       : (console_line_lengths[cy1]));
	  {
	    unsigned short d
	      = ((cx2 < (console_line_lengths[cy2]))
		 ? ((console_line_lengths[cy2]) - cx2)
		 : 0);
	    FASTCOPY ((CHAR_LOC (cx2, cy2)), (CHAR_LOC (x1, cy1)), d);
	    FASTFILL ((CHAR_LOC ((x1 + d), cy1)),
		      (console_width - (x1 + d)),
		      ' ');
	    FASTCOPY ((CHAR_HL (cx2, cy2)), (CHAR_HL (x1, cy1)), d);
	    FASTFILL ((CHAR_HL ((x1 + d), cy1)),
		      (console_width - (x1 + d)),
		      '\0');
	    (console_line_lengths[cy1]) = (x1 + d);
	  }
	  if (cy1 < cy2)
	    {
	      unsigned short d = (console_height - (cy2 + 1));
	      FASTCOPY ((CHAR_LOC (0, (cy2 + 1))),
			(CHAR_LOC (0, (cy1 + 1))),
			(d * console_width));
	      FASTCOPY ((CHAR_HL (0, (cy2 + 1))),
			(CHAR_HL (0, (cy1 + 1))),
			(d * console_width));
	      FASTCOPY ((LINE_LEN_LOC (cy2 + 1)),
			(LINE_LEN_LOC (cy1 + 1)),
			(d * (sizeof (unsigned short))));
	    }
	  if ((cy1 < point_y) || ((cy1 == point_y) && (x1 < point_x)))
	    {
	      if ((cy2 > point_y) || ((cy2 == point_y) && (cx2 >= point_x)))
		{
		  point_x = x1;
		  point_y = cy1;
		}
	      else if (cy2 < point_y)
		point_y -= (cy2 - cy1);
	      else
		point_x -= (cx2 - ((cy1 == cy2) ? x1 : 0));
	      OS2_window_move_cursor (console_wid,
				      (cx2x (point_x)),
				      (cy2y (point_y, 1)));
	    }
	  console_paint (0, console_width, cy1, console_height);
	}
      return (result);
    }
  else
    return (0);
}

static void
compute_marked_region (short x1, short y1, short x2, short y2,
		       unsigned short * cx1, unsigned short * cy1,
		       unsigned short * cx2, unsigned short * cy2)
{
  /* (cx1,cy1) is inclusive, and (cx2,cy2) is exclusive.  */
  unsigned short cx1a = (x2cx (x1, 1));
  unsigned short cy1a = (y2cy (y1, 0));
  unsigned short cx2a = (x2cx (x2, 1));
  unsigned short cy2a = (y2cy (y2, 0));
  if (((cy1a * console_width) + cx1a) > ((cy2a * console_width) + cx2a))
    {
      unsigned short cx = cx1a;
      unsigned short cy = cy1a;
      cx1a = cx2a;
      cy1a = cy2a;
      cx2a = cx;
      cy2a = cy;
    }
  if (cy1a >= console_height)
    {
      cx1a = (console_width - 1);
      cy1a = (console_height - 1);
    }
  else if (cx1a >= console_width)
    cx1a = (console_width - 1);
  if (cy2a >= console_height)
    {
      cx2a = 0;
      cy2a = console_height;
    }
  else if (cx2a > console_width)
    cx2a = console_width;
  (*cx1) = cx1a;
  (*cy1) = cy1a;
  (*cx2) = cx2a;
  (*cy2) = cy2a;
}

static void
highlight_marked_region (unsigned short cx1, unsigned short cy1,
			 unsigned short cx2, unsigned short cy2,
			 char hl)
{
  char * start = (CHAR_HL (cx1, cy1));
  FASTFILL (start, ((CHAR_HL (cx2, cy2)) - start), hl);
}

static void
paint_marked_region_segment (unsigned short x1, unsigned short y1,
			     unsigned short x2, unsigned short y2)
{
  if (y1 == y2)
    console_paint (x1, x2, y1, (y1 + 1));
  else
    {
      console_paint (x1, console_width, y1, (y1 + 1));
      if ((y1 + 1) < y2)
	console_paint (0, console_width, (y1 + 1), y2);
      console_paint (0, x2, y2, (y2 + 1));
    }
}

static void
disable_marked_region (void)
{
  console_marked_region_active_p = 0;
  enable_menu_copy_items (0);
}

static void
enable_menu_copy_items (int enablep)
{
  if (console_menu != NULLHANDLE)
    {
      USHORT value = (enablep ? 0 : MIA_DISABLED);
#if 0
      (void) OS2_menu_set_item_attributes
	(console_pm_qid, console_menu, IDM_CUT, TRUE, MIA_DISABLED, value);
#endif
      (void) OS2_menu_set_item_attributes
	(console_pm_qid, console_menu, IDM_COPY, TRUE, MIA_DISABLED, value);
    }
}

static void
console_resize (unsigned short new_pel_width, unsigned short new_pel_height)
{
  unsigned short new_width = (new_pel_width / CHAR_WIDTH);
  unsigned short new_height = (new_pel_height / CHAR_HEIGHT);
  char * new_chars;
  char * new_highlights;
  unsigned short * new_line_lengths;

  if ((console_chars != 0)
      && (new_width == console_width)
      && (new_height == console_height))
    return;

  new_chars = (OS_malloc (new_width * new_height));
  new_highlights = (OS_malloc (new_width * new_height));
  new_line_lengths = (OS_malloc ((sizeof (unsigned short)) * new_height));

  FASTFILL (new_chars, (new_width * new_height), ' ');
  FASTFILL (new_highlights, (new_width * new_height), '\0');
  FASTFILL (((char *) new_line_lengths),
	    ((sizeof (unsigned short)) * new_height),
	    0);

  if (console_chars != 0)
    {
      unsigned short xlim
	= ((new_width < console_width) ? new_width : console_width);
      unsigned short oy
	= (((point_y + 1) > new_height) ? ((point_y + 1) - new_height) : 0);
      unsigned short oylim
	= (oy + ((new_height < console_height) ? new_height : console_height));
      char * cfrom = (CHAR_LOC (0, oy));
      char * cto = new_chars;
      char * hfrom = (CHAR_HL (0, oy));
      char * hto = new_highlights;
      unsigned short ny = 0;
      while (oy < oylim)
	{
	  FASTCOPY (cfrom, cto, xlim);
	  FASTCOPY (hfrom, hto, xlim);
	  (new_line_lengths[ny]) = (console_line_lengths[oy]);
	  cfrom += console_width;
	  cto += new_width;
	  hfrom += console_width;
	  hto += new_width;
	  oy += 1;
	  ny += 1;
	}
      OS_free (console_chars);
      OS_free (console_highlights);
      OS_free (console_line_lengths);
    }
  console_pel_width = new_pel_width;
  console_pel_height = new_pel_height;
  console_width = new_width;
  console_height = new_height;
  console_chars = new_chars;
  console_highlights = new_highlights;
  console_line_lengths = new_line_lengths;
  if (point_x >= new_width)
    point_x = (new_width - 1);
  if ((point_y + 1) >= new_height)
    point_y -= ((point_y + 1) - new_height);
  OS2_window_move_cursor (console_wid, (cx2x (point_x)), (cy2y (point_y, 1)));
  OS2_window_invalidate (console_wid,
			 0, console_pel_width,
			 0, console_pel_height);
}

static void
console_paint (unsigned short cxl, unsigned short cxh,
	       unsigned short cyl, unsigned short cyh)
{
  if ((cxl < cxh) && (cyl < cyh))
    {
      COLOR foreground = (OS2_ps_get_foreground_color (console_psid));
      COLOR background = (OS2_ps_get_background_color (console_psid));
      unsigned short size = (cxh - cxl);
      char current_hl = '\0';
      while (cyl < cyh)
	{
	  unsigned short x = (cx2x (cxl));
	  unsigned short y = ((cy2y (cyl, 1)) + CHAR_DESCENDER);
	  char * cstart = (CHAR_LOC (cxl, cyl));
	  char * hstart = (CHAR_HL (cxl, cyl));
	  char * hend = (hstart + size);
	  while (hstart < hend)
	    {
	      unsigned short run_length = (compute_run_length (hstart, hend));
	      if (current_hl != (*hstart))
		{
		  if ((*hstart) == '\0')
		    OS2_ps_set_colors (console_psid, foreground, background);
		  else
		    OS2_ps_set_colors (console_psid, background, foreground);
		  current_hl = (*hstart);
		}
	      OS2_ps_draw_text (console_psid, x, y, cstart, run_length);
	      x += (run_length * CHAR_WIDTH);
	      cstart += run_length;
	      hstart += run_length;
	    }
	  cyl += 1;
	}
      if (current_hl != '\0')
	OS2_ps_set_colors (console_psid, foreground, background);
    }
}

static unsigned short
compute_run_length (const char * start, const char * end)
{
  if (start < end)
    {
      const char * scan = start;
      const char c = (*scan++);
      while (scan < end)
	if ((*scan) == c)
	  scan += 1;
	else
	  break;
      return (scan - start);
    }
  else
    return (0);
}

static void
console_clear (unsigned short xl, unsigned short xh,
	       unsigned short yl, unsigned short yh)
{
  OS2_ps_clear (console_psid,
		(cx2x (xl)), (cx2x (xh)),
		(cy2y (yh, 0)), (cy2y (yl, 0)));
}

static void
console_clear_all (void)
{
  OS2_ps_clear (console_psid, 0, console_pel_width, 0, console_pel_height);
}

int
OS2_pm_console_getch (void)
{
  if (console_closedp)
    return (-1);
  if ((readahead_repeat == 0) && (readahead_insert == 0))
    while (1)
      {
	process_events (OS2_msg_fifo_emptyp (pending_events));
	{
	  msg_t * message = (OS2_msg_fifo_remove (pending_events));
	  ULONG msg = (SM_PM_EVENT_MSG (message));
	  MPARAM mp1 = (SM_PM_EVENT_MP1 (message));
	  MPARAM mp2 = (SM_PM_EVENT_MP2 (message));
	  OS2_destroy_message (message);
	  switch (msg)
	    {
	    case WM_CHAR:
	      {
		unsigned short code;
		unsigned char repeat;
		if (translate_key_event (mp1, mp2, (&code), (&repeat)))
		  {
		    /* The feature that causes Delete and Backspace to
		       delete the marked region is disabled because it
		       is too much trouble to make the typeahead
		       buffer conform to the displayed characters.  */
#if 0
		    /* Delete and Backspace must discard the marked
		       region if there is one.  */
		    if ((code == '\177') && (repeat > 0))
		      {
			char * region = (extract_marked_region (1));
			if (region != 0)
			  {
			    OS_free (region);
			    repeat -= 1;
			  }
		      }
#endif
		    if (repeat > 0)
		      {
			readahead_char = code;
			readahead_repeat = repeat;
			goto do_read;
		      }
		  }
	      }
	      break;
	    case WM_CLOSE:
	      switch
		(WinMessageBox
		 (HWND_DESKTOP,
		  NULLHANDLE, /* client window handle */
		  "You have requested that this window be closed.\n\n"
		  "Press \"Yes\" to close this window and terminate Scheme; "
		  "doing so will discard data in unsaved Edwin buffers.\n\n"
		  "Press \"No\" to close only this window, leaving Scheme "
		  "running; the program will continue to run until the "
		  "next time it tries to read from the console.\n\n"
		  "Press \"Cancel\" if you don't want to close this window.",
		  "Terminate Scheme?",
		  0,
		  (MB_YESNOCANCEL | MB_WARNING)))
		  {
		  case MBID_YES:
		    termination_normal (0);
		    break;
		  case MBID_NO:
		    console_closedp = 1;
		    OS2_window_close (console_wid);
		    OS2_close_qid (console_event_qid);
		    OS2_close_std_tqueue (console_tqueue);
		    goto do_read;
		  }
	      break;
	    case WM_COMMAND:
	      {
		ULONG msg = (SHORT1FROMMP (mp1));
		switch (msg)
		  {
		  case IDM_PASTE:
		    if (do_paste ())
		      goto do_read;
		    break;
#if 0
		  /* IDM_CUT is disabled because it is too much
		     trouble to make the typeahead buffer conform to
		     the displayed characters.  */
		  case IDM_CUT:
#endif
		  case IDM_COPY:
		    grab_console_lock ();
		    {
		      char * region = (extract_marked_region (msg == IDM_CUT));
		      if (region != 0)
			{
			  OS2_clipboard_write_text (console_pm_qid, region);
			  OS_free (region);
			  unmark_marked_region ();
			}
		    }
		    release_console_lock ();
		    break;
		  }
	      }
	      break;
	    }
	}
      }
 do_read:
  if (readahead_insert != 0)
    {
      char c = (*readahead_insert_scan++);
      if ((*readahead_insert_scan) == '\0')
	{
	  OS_free ((void *) readahead_insert);
	  readahead_insert = 0;
	}
      return (c);
    }
  if (readahead_repeat != 0)
    {
      readahead_repeat -= 1;
      return (readahead_char);
    }
  return (-1);
}

static int
do_paste (void)
{
  const char * text = (OS2_clipboard_read_text (console_pm_qid));
  if ((text != 0) && ((*text) != '\0'))
    {
      readahead_insert = text;
      readahead_insert_scan = text;
      return (1);
    }
  else
    {
      OS_free ((void *) text);
      return (0);
    }
}

static int
translate_key_event (MPARAM mp1, MPARAM mp2,
		     unsigned short * code, unsigned char * repeat)
{
  unsigned short flags;
  if (!OS2_translate_wm_char (mp1, mp2, code, (&flags), repeat))
    return (0);
  if ((flags & KC_VIRTUALKEY) != 0)
    switch (*code)
      {
      case VK_BACKSPACE:
      case VK_DELETE:
	(*code) = '\177';
	break;
      case VK_TAB:
	(*code) = '\t';
	break;
      case VK_ESC:
	(*code) = '\033';
	break;
      case VK_SPACE:
	(*code) = ' ';
	break;
      case VK_NEWLINE:
      case VK_ENTER:
	(*code) = '\r';
	break;
      default:
	return (0);
      }
  if (((*code) >= 0200) || ((flags & KC_ALT) != 0))
    return (0);
  if ((flags & KC_CTRL) != 0)
    if ((*code) >= 040)
      (*code) &= 037;
    else
      return (0);
  if ((*code) == 0)
    return (0);
  return (1);
}

void
OS2_pm_console_write (const char * data, size_t size)
{
  const char * end = (data + size);
  const char * nonprint;
  if (console_closedp)
    return;
  grab_console_lock ();
  unmark_marked_region ();
  while (data < end)
    {
      nonprint = (find_nonprint (data, end));
      if (data < nonprint)
	while (1)
	  {
	    unsigned short size = (nonprint - data);
	    if (size > (console_width - point_x))
	      size = (console_width - point_x);
	    FASTCOPY (data, (CHAR_LOC (point_x, point_y)), size);
	    FASTFILL ((CHAR_HL (point_x, point_y)), size, '\0');
	    OS2_ps_draw_text (console_psid,
			      (cx2x (point_x)),
			      ((cy2y (point_y, 1)) + CHAR_DESCENDER),
			      data,
			      size);
	    data += size;
	    point_x += size;
	    (console_line_lengths[point_y]) = point_x;
	    if (point_x == console_width)
	      {
		do_carriage_return ();
		do_linefeed ();
	      }
	    if (data == nonprint)
	      break;
	  }
      if (data < end)
	switch (*data++)
	  {
	  case '\r':
	    do_carriage_return ();
	    break;
	  case '\012':
	    do_linefeed ();
	    break;
	  case '\f':
	    do_formfeed ();
	    break;
	  case '\b':
	    do_backspace ();
	    break;
	  case '\a':
	    do_alert ();
	    break;
	  }
    }
  OS2_window_move_cursor (console_wid, (cx2x (point_x)), (cy2y (point_y, 1)));
  release_console_lock ();
}

static const char *
find_nonprint (const char * start, const char * end)
{
  while (start < end)
    if (!isprint (*start++))
      return (--start);
  return (end);
}

static void
do_carriage_return (void)
{
  point_x = 0;
}

static void
do_linefeed (void)
{
  if (point_y < (console_height - 1))
    point_y += 1;
  else
    {
#ifdef CONSOLE_WRAP
      point_y = 0;
#else /* not CONSOLE_WRAP */
      point_y = (console_height - 1);
      FASTCOPY ((CHAR_LOC (0, 1)),
		(CHAR_LOC (0, 0)),
		(point_y * console_width));
      FASTCOPY ((CHAR_HL (0, 1)),
		(CHAR_HL (0, 0)),
		(point_y * console_width));
      FASTCOPY ((LINE_LEN_LOC (1)),
		(LINE_LEN_LOC (0)),
		(point_y * (sizeof (unsigned short))));
      OS2_window_scroll (console_wid,
			 0, console_pel_width,
			 0, (point_y * CHAR_HEIGHT),
			 0, CHAR_HEIGHT);
#endif /* not CONSOLE_WRAP */
    }
  FASTFILL ((CHAR_LOC (0, point_y)), console_width, ' ');
  FASTFILL ((CHAR_HL (0, point_y)), console_width, '\0');
  (console_line_lengths[point_y]) = 0;
  console_clear (0, console_width, point_y, (point_y + 1));
}

static void
do_formfeed (void)
{
  point_x = 0;
  point_y = 0;
  FASTFILL ((CHAR_LOC (0, 0)), (console_height * console_width), ' ');
  FASTFILL ((CHAR_HL (0, 0)), (console_height * console_width), '\0');
  FASTFILL ((LINE_LEN_LOC (0)),
	    (console_height * (sizeof (unsigned short))),
	    0);
  console_clear_all ();
}

static void
do_backspace (void)
{
  if (point_x > 0)
    {
      point_x -= 1;
      (console_line_lengths[point_y]) = point_x;
    }
}

static void
do_alert (void)
{
  WinAlarm (HWND_DESKTOP, WA_ERROR);
}
