/* Lisp Editor and Listener Window Class */

#include <windows.h>
#include <windowsx.h>
#include <string.h>
#include <ctype.h>
#ifdef NOTTY
#include <dde.h>
#endif /* NOTTY */
#include "ledit.h"
#include "winutils.h"

static HFONT hFixedFont;
static HWND hTTYWnd = NULL;
static void (*MainLoop)(void);
static WNDPROC fpOldEditProc = NULL, fpLEditProc = NULL;

/* input and output buffers */
#define BUFSIZE 255
static char obuf[BUFSIZE];
#ifdef NOTTY
#define instart 0
#else
static char *obp = obuf;
static int instart = 0, inend = 0;
#endif /* NOTTY */

/* TTY Trimming defines */
#define MAXTTYBUF 25000
#define TRIMTTYTO 20000

LONG CALLBACK LEditWndProc(HWND, UINT, WPARAM, LONG);
#ifndef NOTTY
static void waitforline(void);
static BOOL check_parens(char *, int);
static BOOL at_text_end(char *, int);
static BOOL has_return(char *, int);
static BOOL input_complete(int);
static void check_trim_buffer(int);
#endif /* NOTTY */
static BOOL flash_matching_paren(HWND, int);
static void pardelay(void);
static void do_tab(HWND, int);
static void fix_blanks(HWND, int, int);
static int num_to_skip(char *, int);
static BOOL is_special(char *, int);
static void edit_getsel(HWND, UINT *, UINT *);

static char *LockText(void);
static void UnlockText(void);

/**************************************************************************/
/**************************************************************************/
/**                                                                      **/
/**                         Public Routines                              **/
/**                                                                      **/
/**************************************************************************/
/**************************************************************************/

void InitLEditClass(void (*f)(), HFONT font)
{
  MainLoop = f;

  hFixedFont = (font) ? font : GetStockObject(ANSI_FIXED_FONT);
}

HWND CreateLEditWindow(HWND hWndParent, HMENU hMenu, HANDLE hInstance)
{
  RECT Rect;
  HWND hWnd;
  HANDLE h;

  /* This generates a global handle tobe used in place of hInstance. */
  /* This forces the edit control to use the global hewp with this   */
  /* handle for its text. It looks like this call works in Win32s    */
  /* too, but is should not be necessary there, so I have ifdefed it */
  /* out for now.                                                    */
#ifdef WIN32
  h = hInstance;
#else
  h = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, 1024L),
#endif /* WIN32 */

  GetClientRect(hWndParent, (LPRECT) &Rect);
  hWnd = CreateWindow("Edit",
		      NULL,
		      WS_CHILD | WS_VISIBLE | WS_VSCROLL |
		      ES_MULTILINE | ES_AUTOVSCROLL,
		      0,
		      0,
		      (Rect.right - Rect.left),
		      (Rect.bottom - Rect.top),
		      hWndParent,
		      hMenu,
		      h,
		      NULL);

  if (hWnd) {
    if (fpOldEditProc == NULL) {
      fpOldEditProc = XLSGetWindowProc(hWnd);
      fpLEditProc = (WNDPROC) MakeProcInstance((FARPROC) LEditWndProc,
    		                               hInstance);
    }
    (void) SubclassWindow(hWnd, fpLEditProc);

    /* "First" window created is TTY */
    if (hTTYWnd == NULL) hTTYWnd = hWnd;
    SetWindowFont(hWnd, hFixedFont, FALSE);
  }

  return(hWnd);
}

#ifndef NOTTY
BOOL TTYHasInput(void)
{
  return(instart < inend ? TRUE : FALSE);
}

void TTYPutStr(char *s)
{
  while (*s != '\0') TTYPutC((int) *s++);
  TTYFlushOutput();
}

int TTYPutC(int c)
{
  if (obp >= obuf + BUFSIZE - 3) TTYFlushOutput();
  if (c == '\n' || c == '\r') {
    *obp++ = '\r';
    *obp++ = '\n';
    TTYFlushOutput();
  }
  else *obp++ = c;
  return(c);
}

void TTYResetInput(void)
{
  instart = Edit_GetTextLength(hTTYWnd);
  inend = instart;
  Edit_SetSel(hTTYWnd, instart, instart);
}

void TTYFlushOutput(void)
{
  int selstart, selend;

  if (obp > obuf) {
    *obp = '\0';
    obp = obuf;
    selstart = Edit_GetTextLength(hTTYWnd);
    selend = selstart + strlen(obuf);
    Edit_SetSel(hTTYWnd, selstart, selend);
    Edit_ReplaceSel(hTTYWnd, obuf);
    TTYResetInput();
    check_trim_buffer(TRUE);
  }
}

void TTYFlush(void)
{
  TTYFlushOutput();
  TTYResetInput();
}

int TTYGetC(void)
{
  int c;
  char *pText;

  /* wait for input if there is none */
  if (! TTYHasInput()) waitforline();

  /* get the text */
  pText = LockText();
  if (! pText) { SysBeep(10); return('\0'); }

  /* skip linefeeds (or whatever they are) */
  while (pText[instart] == '\r') instart++;

  if (instart < inend)
    c = pText[instart++];
  else {
    SysBeep(10);
    c = '\0';
  }

  /* release the text buffer */
  UnlockText();

  /* reset the enput at the end of the last line */
  if (instart >= inend) TTYResetInput();

  return((c == '\r') ? '\n' : c);
}
#endif /* NOTTY */

BOOL TTYHasSelection(void)
{
  UINT start, end;

  edit_getsel(hTTYWnd, &start, &end);
  return(start < end ? TRUE : FALSE);
}

void TTYSelToClip(void)
{
  XLSEditCopy(hTTYWnd);
}

void TTYClearSel(void)
{
#ifndef NOTTY
  UINT selstart;

  edit_getsel(hTTYWnd, &selstart, NULL);

  if (selstart < instart)
    SysBeep(10);
  else
#endif /* NOTTY */
    XLSEditClear(hTTYWnd);
}

void TTYPasteFromClip(void)
{
#ifndef NOTTY
  UINT selstart;

  edit_getsel(hTTYWnd, &selstart, NULL);

  /* move the insertion point to the end if it is */
  /* before the input text start                  */
  if (selstart < instart) {
    int text_end = Edit_GetTextLength(hTTYWnd);
    Edit_SetSel(hTTYWnd, text_end, text_end);
  }
#endif /* NOTTY */

  XLSEditPaste(hTTYWnd);

#ifndef NOTTY
  /* check for a return and a complete expression */
  if (input_complete(TRUE)) {
    inend = Edit_GetTextLength(hTTYWnd);
  }
#endif /* NOTTY */
}

#ifdef NOTTY
/**** This relies on being able to modify the buffer */
/**** and on the fact that an unlock does nothing */
char *TTYSelectionStr()
{
  UINT selstart, selend;
  char *p;

  edit_getsel(hTTYWnd, &selstart, &selend);
  p = LockText();
  p += selstart;
  p[(int)(selend - selstart)] = '\0';
  return p;
}
#else
void TTYTrimBuffer(void)
{
  check_trim_buffer(FALSE);
}
#endif /* NOTTY */

/**************************************************************************/
/**************************************************************************/
/**                                                                      **/
/**                    Subclass Callback Function                        **/
/**                                                                      **/
/**************************************************************************/
/**************************************************************************/

/* window function for LEdit class -- filters returns */
LONG CALLBACK LEditWndProc(HWND hWnd, UINT message, WPARAM wParam, LONG lParam)
{
  if (message == WM_CHAR) {
#ifndef NOTTY
    UINT selstart, selend;

    edit_getsel(hTTYWnd, &selstart, &selend);

    /* ignore backspaces at the start of the input region */
    if (selstart == instart && selstart == selend && wParam == '\b') {
      SysBeep(10);
      return(FALSE);
    }

    /* move the insertion point to the end if it is before the */
    /* input text start                                        */
    if (selstart < instart) {
      int text_end = Edit_GetTextLength(hTTYWnd);
      Edit_SetSel(hTTYWnd, text_end, text_end);
    }
#endif /* NOTTY */

    /* adjust spacing on tab */
    if (wParam == '\t') {
      do_tab(hTTYWnd, instart);
      return(FALSE);
    }

#ifndef NOTTY
    /* jump to end of input on shift-return */
    if (wParam == '\r' && HIBIT(GetKeyState(VK_SHIFT))) {
      selstart = selend = Edit_GetTextLength(hTTYWnd);
      Edit_SetSel(hTTYWnd, selstart, selend);
      return(FALSE);
    }
#endif /* NOTTY */

    /* insert the character using the inherited method */
    CallWindowProc(fpOldEditProc, hWnd, message, wParam, lParam);

    /* flash matching parenthesis */
    if (wParam == ')') flash_matching_paren(hTTYWnd, instart);

#ifndef NOTTY
    /* check the character for a return */
    if (wParam == '\r' && input_complete(FALSE)) {
      inend = Edit_GetTextLength(hTTYWnd);
      return(TRUE);
    }
    else return(FALSE);
#else
    return(TRUE);
#endif /* NOTTY */
  }
  else return(CallWindowProc(fpOldEditProc, hWnd, message, wParam, lParam));
}

/**************************************************************************/
/**************************************************************************/
/**                                                                      **/
/**                         Internal Routines                            **/
/**                                                                      **/
/**************************************************************************/
/**************************************************************************/

#ifndef NOTTY
static void waitforline(void)
{
  TTYFlushOutput();
  TTYResetInput();
  (*MainLoop)();
}

static BOOL input_complete(int check_return)
{
  char *pText;
  BOOL result;
  UINT text_end, selstart;
  BOOL checkstart;

  text_end = Edit_GetTextLength(hTTYWnd);
  pText = LockText();
  if (! pText) { SysBeep(10); return(FALSE); }
  edit_getsel(hTTYWnd, &selstart, NULL);
  checkstart = (selstart > 0) ? selstart - 1 : selstart;

  result = check_parens(pText + instart, text_end - instart)
	   && at_text_end(pText + selstart, text_end - selstart)
	   && (! check_return
	       || has_return(pText + checkstart, text_end - checkstart));

  UnlockText();
  return(result);
}

static BOOL at_text_end(char *s, int n)
{
  int i, result = TRUE;

  for (i = 0; result && i < n; i++)
    if (! isspace(s[i]) && s[i] != '\n' && s[i] != '\r')
      result = FALSE;
  return(result);
}

static BOOL has_return(char *s, int n)
{
  int i, result = FALSE;

  for (i = 0; ! result && i < n; i++)
    if (s[i] == '\n' || s[i] == '\r')
      result = TRUE;
  return(result);
}

static BOOL check_parens(s, n)
	char *s;
	int n;
{
  int parcount = 0, inquotes = FALSE, incomment = FALSE;
  char ch;
   
  while (n-- > 0) {
    ch = *s++;
    switch (ch) {
    case  '"': inquotes = ! inquotes; break;
    case  ';': if (! inquotes) incomment = TRUE; break;
    case '\r':
    case '\n': incomment = FALSE; break;
    case  '(': if (! inquotes && ! incomment) parcount++; break;
    case  ')': if (! inquotes && ! incomment) parcount--; break;
    }
  }
  return (parcount <= 0 ? TRUE : FALSE);
}
#endif /* NOTTY */

static BOOL flash_matching_paren(HWND hWnd, int start)
{
  BOOL inquotes = FALSE;
  UINT parcount = 0, sel, par;
  char ch, *s;

  edit_getsel(hWnd, &sel, NULL);

  s = LockText();
  if (! s) { SysBeep(10); return(FALSE); }
  s = s + sel - 1;

  par = sel;
  do {
    par--;
    ch = *s--;
    switch (ch) {
    case  '"': inquotes = ! inquotes; break;
    case  '(': if (! inquotes) parcount--; break;
    case  ')': if (! inquotes) parcount++; break;
    }
  } while (par > start && parcount > 0);

  UnlockText();

  if (ch == '(' && parcount == 0) {
    Edit_SetSel(hWnd, par, par + 1);
    pardelay();
    Edit_SetSel(hWnd, sel, sel);
  }

  return (parcount <= 0 ? TRUE : FALSE);
}

static void pardelay(void)
{
  Delay(250);
}

#define ISRETURNCHAR(c) ((c) == '\r' || (c) == '\n')

static void do_tab(HWND hWnd, int start)
{
  BOOL inquote;
  UINT sel, curline, lastline, nblanks, length;
  int parcount, pos;
  char *s, ch;

  edit_getsel(hWnd, &sel, NULL);

  s = LockText();
  if (! s) { SysBeep(10); return; }
  length = Edit_GetTextLength(hWnd);

  /* find beginning of the line */
  curline = sel;
  while (curline > start && ! ISRETURNCHAR(s[curline - 1])) curline--;
  if (curline == start) return;

  /* find unmatched paren */
  parcount = 0;
  inquote = FALSE;
  pos = curline;
  while (parcount >= 0 && --pos >= start) {
    ch = s[pos];
    switch (ch) {
    case ')': if (! inquote) parcount++; break;
    case '(': if (! inquote) parcount--; break;
    case '"': inquote = ! inquote; break;
    }
  }
  if (parcount == 0) return;
  
  /* find beginning of the line containing the expression start */
  lastline = pos;
  while (lastline > 0 && ! ISRETURNCHAR(s[lastline - 1])) lastline--;

  /* skip forward an s-expression or to first non blank */
  pos += num_to_skip(s + pos, curline - pos);

  if (pos > curline) pos = curline;
  nblanks = pos - lastline;

  /* adjust for the number of blanks already present, replace tabs by blanks */
  for (pos = curline; 
       pos < length && (s[pos] == ' ' || s[pos] == '\t');
       nblanks--, pos++)
    if (s[pos] == '\t') s[pos] = ' ';

  UnlockText();

  /* insert or delete the appropriate number of blanks */
  if (nblanks == 0) return;

  sel += nblanks;
  if (pos > length) pos = length;
  Edit_SetSel(hWnd, pos, pos);
  fix_blanks(hWnd, nblanks, curline);
  length = Edit_GetTextLength(hWnd);
  if (pos > length) pos = length;
  Edit_SetSel(hWnd, sel, sel);
}

static void fix_blanks(HWND hWnd, int nblanks, int curline)
{
  int i;

  if (nblanks > 0) {
    for (i = 0; i < nblanks && i < BUFSIZE -3; i++) obuf[i] = ' ';
    obuf[i] = '\0';
    Edit_SetSel(hWnd, curline, curline);
    Edit_ReplaceSel(hWnd, obuf);
  }
  else {
    obuf[0] = '\0';
    Edit_SetSel(hWnd, curline, curline - nblanks);
    Edit_ReplaceSel(hWnd, obuf);
  }
}

static int num_to_skip(char *s, int n)
{
  char str[4];
  int i, pos, oldpos;

  pos = 0;
  
  if (n <= 0) pos = 0;
  else if (*s == '(') {
  
    s++; n--; pos = 1;
    
    /* skip blanks */
    while (n > 0 && (*s == ' ' || *s == '\t')) { s++; n--; pos++; }
    
    /* check for end of line or list or lisp comment*/
    if (n > 0 && ! ISRETURNCHAR(*s) && *s != ';' && *s != '(') {
    
      /* check for special symbols */
      for (i = 0; i < 3 && i < n; i++)
        str[i] = islower(s[i]) ? (char) toupper(s[i]) : s[i];
      str[i] = '\0';
      if (is_special(s, n) /* strcmp(str, "DEF") == 0 || strcmp(str, "LET") == 0 
          || strcmp(str, "FLE") == 0 */ )
        pos = 2;
      else {
        
        /* skip over the s-expression */
        oldpos = pos;
	while (n > 0 && *s != ' ' && *s != '\t' && ! ISRETURNCHAR(*s))
          { s++; n--; pos++; }
          
        /* check for another s expression */
        for (i = 0; n > 0 && (*s == ' ' || *s == '\t'); s++, n--, i++) ;
	if (n == 0 || ISRETURNCHAR(*s))
	  pos = (oldpos == pos) ? oldpos + 1 : oldpos;
        else pos += i;
      }
    }
  }
  else {
    
    /* skip over any blanks */
    for (i = 0; n > 0 && (*s == ' ' || *s == '\t'); s++, n--, i++) ;
    if (n > 0 && ! ISRETURNCHAR(*s)) pos += i;
  }
  return(pos);
}

static BOOL is_special(char *s, int n)
{
  char str[10];
  int i;

  for (i = 0; i < n && i < 9; i++)
    str[i] = islower(s[i]) ? (char) toupper(s[i]) : s[i];
  str[i] = '\0';

  if (n >= 5 && strncmp(str, "DEFUN", 5) == 0) return(TRUE);
  if (n >= 8 && strncmp(str, "DEFMACRO", 8) == 0) return(TRUE);
  if (n >= 7 && strncmp(str, "DEFMETH", 7) == 0) return(TRUE);
  if (n >= 8 && strncmp(str, "DEFPROTO", 8) == 0) return(TRUE);
  if (n >= 3 && strncmp(str, "LET", 3) == 0) return(TRUE);
  if (n >= 4 && strncmp(str, "FLET", 4) == 0) return(TRUE);
  if (n >= 4 && strncmp(str, "COND", 4) == 0) return(TRUE);
  if (n >= 4 && strncmp(str, "CASE", 4) == 0) return(TRUE);
  if (n >= 6 && strncmp(str, "LABELS", 6) == 0) return(TRUE);
  if (n >= 6 && strncmp(str, "LAMBDA", 6) == 0) return(TRUE);
  return(FALSE);
}

#ifndef NOTTY
static void check_trim_buffer(int maxonly)
{
  int length, start;
  UINT selstart, selend;

  length = Edit_GetTextLength(hTTYWnd);

  if (length > (maxonly ? MAXTTYBUF : TRIMTTYTO)) {
    start = length - TRIMTTYTO;
    edit_getsel(hTTYWnd, &selstart, &selend);
    obuf[0] = '\0';
    Edit_SetSel(hTTYWnd, 0, start);
    Edit_ReplaceSel(hTTYWnd, obuf);
    instart -= start;
    inend -= start;
    selstart -= start;
    selend -= start;
    Edit_SetSel(hTTYWnd, selstart, selend);
  }
}
#endif /* NOTTY */

static void edit_getsel(HWND hWnd, UINT *pstart, UINT *pend)
{
  long sel;

  /**** this doesn't make sense for edit records larget than 64K */
  sel = Edit_GetSel(hWnd);
  if (pstart != NULL) *pstart = LOWORD(sel);
  if (pend != NULL) *pend = HIWORD(sel);
}

#define TBUFSIZ 31000
static char *tbuf = NULL;

static char *LockText(void)
{
  size_t length;

  if (tbuf == NULL) {
    HANDLE h;
    h = GlobalAlloc(GMEM_MOVEABLE, TBUFSIZ + 1);
    tbuf = h ? GlobalLock(h) : 0;
    if (tbuf == NULL) return NULL;
  }

  length = Edit_GetTextLength(hTTYWnd);
  GetWindowText(hTTYWnd, tbuf, TBUFSIZ);
  tbuf[length] = '\0';
  return tbuf;
}

static void UnlockText(void) {}


syntax highlighted by Code2HTML, v. 0.9.1