Initial commit, 3-52-19 alpha
[cls.git] / src / c / mswin / ledit.c
blobf070737ce3edfb3a2c11fb348e0f7f85ed368d76
1 /* Lisp Editor and Listener Window Class */
3 #include <windows.h>
4 #include <windowsx.h>
5 #include <string.h>
6 #include <ctype.h>
7 #ifdef NOTTY
8 #include <dde.h>
9 #endif /* NOTTY */
10 #include "ledit.h"
11 #include "winutils.h"
13 static HFONT hFixedFont;
14 static HWND hTTYWnd = NULL;
15 static void (*MainLoop)(void);
16 static WNDPROC fpOldEditProc = NULL, fpLEditProc = NULL;
18 /* input and output buffers */
19 #define BUFSIZE 255
20 static char obuf[BUFSIZE];
21 #ifdef NOTTY
22 #define instart 0
23 #else
24 static char *obp = obuf;
25 static int instart = 0, inend = 0;
26 #endif /* NOTTY */
28 /* TTY Trimming defines */
29 #define MAXTTYBUF 25000
30 #define TRIMTTYTO 20000
32 LONG CALLBACK LEditWndProc(HWND, UINT, WPARAM, LONG);
33 #ifndef NOTTY
34 static void waitforline(void);
35 static BOOL check_parens(char *, int);
36 static BOOL at_text_end(char *, int);
37 static BOOL has_return(char *, int);
38 static BOOL input_complete(int);
39 static void check_trim_buffer(int);
40 #endif /* NOTTY */
41 static BOOL flash_matching_paren(HWND, int);
42 static void pardelay(void);
43 static void do_tab(HWND, int);
44 static void fix_blanks(HWND, int, int);
45 static int num_to_skip(char *, int);
46 static BOOL is_special(char *, int);
47 static void edit_getsel(HWND, UINT *, UINT *);
49 static char *LockText(void);
50 static void UnlockText(void);
52 /**************************************************************************/
53 /**************************************************************************/
54 /** **/
55 /** Public Routines **/
56 /** **/
57 /**************************************************************************/
58 /**************************************************************************/
60 void InitLEditClass(void (*f)(), HFONT font)
62 MainLoop = f;
64 hFixedFont = (font) ? font : GetStockObject(ANSI_FIXED_FONT);
67 HWND CreateLEditWindow(HWND hWndParent, HMENU hMenu, HANDLE hInstance)
69 RECT Rect;
70 HWND hWnd;
71 HANDLE h;
73 /* This generates a global handle tobe used in place of hInstance. */
74 /* This forces the edit control to use the global hewp with this */
75 /* handle for its text. It looks like this call works in Win32s */
76 /* too, but is should not be necessary there, so I have ifdefed it */
77 /* out for now. */
78 #ifdef WIN32
79 h = hInstance;
80 #else
81 h = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, 1024L),
82 #endif /* WIN32 */
84 GetClientRect(hWndParent, (LPRECT) &Rect);
85 hWnd = CreateWindow("Edit",
86 NULL,
87 WS_CHILD | WS_VISIBLE | WS_VSCROLL |
88 ES_MULTILINE | ES_AUTOVSCROLL,
91 (Rect.right - Rect.left),
92 (Rect.bottom - Rect.top),
93 hWndParent,
94 hMenu,
96 NULL);
98 if (hWnd) {
99 if (fpOldEditProc == NULL) {
100 fpOldEditProc = XLSGetWindowProc(hWnd);
101 fpLEditProc = (WNDPROC) MakeProcInstance((FARPROC) LEditWndProc,
102 hInstance);
104 (void) SubclassWindow(hWnd, fpLEditProc);
106 /* "First" window created is TTY */
107 if (hTTYWnd == NULL) hTTYWnd = hWnd;
108 SetWindowFont(hWnd, hFixedFont, FALSE);
111 return(hWnd);
114 #ifndef NOTTY
115 BOOL TTYHasInput(void)
117 return(instart < inend ? TRUE : FALSE);
120 void TTYPutStr(char *s)
122 while (*s != '\0') TTYPutC((int) *s++);
123 TTYFlushOutput();
126 int TTYPutC(int c)
128 if (obp >= obuf + BUFSIZE - 3) TTYFlushOutput();
129 if (c == '\n' || c == '\r') {
130 *obp++ = '\r';
131 *obp++ = '\n';
132 TTYFlushOutput();
134 else *obp++ = c;
135 return(c);
138 void TTYResetInput(void)
140 instart = Edit_GetTextLength(hTTYWnd);
141 inend = instart;
142 Edit_SetSel(hTTYWnd, instart, instart);
145 void TTYFlushOutput(void)
147 int selstart, selend;
149 if (obp > obuf) {
150 *obp = '\0';
151 obp = obuf;
152 selstart = Edit_GetTextLength(hTTYWnd);
153 selend = selstart + strlen(obuf);
154 Edit_SetSel(hTTYWnd, selstart, selend);
155 Edit_ReplaceSel(hTTYWnd, obuf);
156 TTYResetInput();
157 check_trim_buffer(TRUE);
161 void TTYFlush(void)
163 TTYFlushOutput();
164 TTYResetInput();
167 int TTYGetC(void)
169 int c;
170 char *pText;
172 /* wait for input if there is none */
173 if (! TTYHasInput()) waitforline();
175 /* get the text */
176 pText = LockText();
177 if (! pText) { SysBeep(10); return('\0'); }
179 /* skip linefeeds (or whatever they are) */
180 while (pText[instart] == '\r') instart++;
182 if (instart < inend)
183 c = pText[instart++];
184 else {
185 SysBeep(10);
186 c = '\0';
189 /* release the text buffer */
190 UnlockText();
192 /* reset the enput at the end of the last line */
193 if (instart >= inend) TTYResetInput();
195 return((c == '\r') ? '\n' : c);
197 #endif /* NOTTY */
199 BOOL TTYHasSelection(void)
201 UINT start, end;
203 edit_getsel(hTTYWnd, &start, &end);
204 return(start < end ? TRUE : FALSE);
207 void TTYSelToClip(void)
209 XLSEditCopy(hTTYWnd);
212 void TTYClearSel(void)
214 #ifndef NOTTY
215 UINT selstart;
217 edit_getsel(hTTYWnd, &selstart, NULL);
219 if (selstart < instart)
220 SysBeep(10);
221 else
222 #endif /* NOTTY */
223 XLSEditClear(hTTYWnd);
226 void TTYPasteFromClip(void)
228 #ifndef NOTTY
229 UINT selstart;
231 edit_getsel(hTTYWnd, &selstart, NULL);
233 /* move the insertion point to the end if it is */
234 /* before the input text start */
235 if (selstart < instart) {
236 int text_end = Edit_GetTextLength(hTTYWnd);
237 Edit_SetSel(hTTYWnd, text_end, text_end);
239 #endif /* NOTTY */
241 XLSEditPaste(hTTYWnd);
243 #ifndef NOTTY
244 /* check for a return and a complete expression */
245 if (input_complete(TRUE)) {
246 inend = Edit_GetTextLength(hTTYWnd);
248 #endif /* NOTTY */
251 #ifdef NOTTY
252 /**** This relies on being able to modify the buffer */
253 /**** and on the fact that an unlock does nothing */
254 char *TTYSelectionStr()
256 UINT selstart, selend;
257 char *p;
259 edit_getsel(hTTYWnd, &selstart, &selend);
260 p = LockText();
261 p += selstart;
262 p[(int)(selend - selstart)] = '\0';
263 return p;
265 #else
266 void TTYTrimBuffer(void)
268 check_trim_buffer(FALSE);
270 #endif /* NOTTY */
272 /**************************************************************************/
273 /**************************************************************************/
274 /** **/
275 /** Subclass Callback Function **/
276 /** **/
277 /**************************************************************************/
278 /**************************************************************************/
280 /* window function for LEdit class -- filters returns */
281 LONG CALLBACK LEditWndProc(HWND hWnd, UINT message, WPARAM wParam, LONG lParam)
283 if (message == WM_CHAR) {
284 #ifndef NOTTY
285 UINT selstart, selend;
287 edit_getsel(hTTYWnd, &selstart, &selend);
289 /* ignore backspaces at the start of the input region */
290 if (selstart == instart && selstart == selend && wParam == '\b') {
291 SysBeep(10);
292 return(FALSE);
295 /* move the insertion point to the end if it is before the */
296 /* input text start */
297 if (selstart < instart) {
298 int text_end = Edit_GetTextLength(hTTYWnd);
299 Edit_SetSel(hTTYWnd, text_end, text_end);
301 #endif /* NOTTY */
303 /* adjust spacing on tab */
304 if (wParam == '\t') {
305 do_tab(hTTYWnd, instart);
306 return(FALSE);
309 #ifndef NOTTY
310 /* jump to end of input on shift-return */
311 if (wParam == '\r' && HIBIT(GetKeyState(VK_SHIFT))) {
312 selstart = selend = Edit_GetTextLength(hTTYWnd);
313 Edit_SetSel(hTTYWnd, selstart, selend);
314 return(FALSE);
316 #endif /* NOTTY */
318 /* insert the character using the inherited method */
319 CallWindowProc(fpOldEditProc, hWnd, message, wParam, lParam);
321 /* flash matching parenthesis */
322 if (wParam == ')') flash_matching_paren(hTTYWnd, instart);
324 #ifndef NOTTY
325 /* check the character for a return */
326 if (wParam == '\r' && input_complete(FALSE)) {
327 inend = Edit_GetTextLength(hTTYWnd);
328 return(TRUE);
330 else return(FALSE);
331 #else
332 return(TRUE);
333 #endif /* NOTTY */
335 else return(CallWindowProc(fpOldEditProc, hWnd, message, wParam, lParam));
338 /**************************************************************************/
339 /**************************************************************************/
340 /** **/
341 /** Internal Routines **/
342 /** **/
343 /**************************************************************************/
344 /**************************************************************************/
346 #ifndef NOTTY
347 static void waitforline(void)
349 TTYFlushOutput();
350 TTYResetInput();
351 (*MainLoop)();
354 static BOOL input_complete(int check_return)
356 char *pText;
357 BOOL result;
358 UINT text_end, selstart;
359 BOOL checkstart;
361 text_end = Edit_GetTextLength(hTTYWnd);
362 pText = LockText();
363 if (! pText) { SysBeep(10); return(FALSE); }
364 edit_getsel(hTTYWnd, &selstart, NULL);
365 checkstart = (selstart > 0) ? selstart - 1 : selstart;
367 result = check_parens(pText + instart, text_end - instart)
368 && at_text_end(pText + selstart, text_end - selstart)
369 && (! check_return
370 || has_return(pText + checkstart, text_end - checkstart));
372 UnlockText();
373 return(result);
376 static BOOL at_text_end(char *s, int n)
378 int i, result = TRUE;
380 for (i = 0; result && i < n; i++)
381 if (! isspace(s[i]) && s[i] != '\n' && s[i] != '\r')
382 result = FALSE;
383 return(result);
386 static BOOL has_return(char *s, int n)
388 int i, result = FALSE;
390 for (i = 0; ! result && i < n; i++)
391 if (s[i] == '\n' || s[i] == '\r')
392 result = TRUE;
393 return(result);
396 static BOOL check_parens(s, n)
397 char *s;
398 int n;
400 int parcount = 0, inquotes = FALSE, incomment = FALSE;
401 char ch;
403 while (n-- > 0) {
404 ch = *s++;
405 switch (ch) {
406 case '"': inquotes = ! inquotes; break;
407 case ';': if (! inquotes) incomment = TRUE; break;
408 case '\r':
409 case '\n': incomment = FALSE; break;
410 case '(': if (! inquotes && ! incomment) parcount++; break;
411 case ')': if (! inquotes && ! incomment) parcount--; break;
414 return (parcount <= 0 ? TRUE : FALSE);
416 #endif /* NOTTY */
418 static BOOL flash_matching_paren(HWND hWnd, int start)
420 BOOL inquotes = FALSE;
421 UINT parcount = 0, sel, par;
422 char ch, *s;
424 edit_getsel(hWnd, &sel, NULL);
426 s = LockText();
427 if (! s) { SysBeep(10); return(FALSE); }
428 s = s + sel - 1;
430 par = sel;
431 do {
432 par--;
433 ch = *s--;
434 switch (ch) {
435 case '"': inquotes = ! inquotes; break;
436 case '(': if (! inquotes) parcount--; break;
437 case ')': if (! inquotes) parcount++; break;
439 } while (par > start && parcount > 0);
441 UnlockText();
443 if (ch == '(' && parcount == 0) {
444 Edit_SetSel(hWnd, par, par + 1);
445 pardelay();
446 Edit_SetSel(hWnd, sel, sel);
449 return (parcount <= 0 ? TRUE : FALSE);
452 static void pardelay(void)
454 Delay(250);
457 #define ISRETURNCHAR(c) ((c) == '\r' || (c) == '\n')
459 static void do_tab(HWND hWnd, int start)
461 BOOL inquote;
462 UINT sel, curline, lastline, nblanks, length;
463 int parcount, pos;
464 char *s, ch;
466 edit_getsel(hWnd, &sel, NULL);
468 s = LockText();
469 if (! s) { SysBeep(10); return; }
470 length = Edit_GetTextLength(hWnd);
472 /* find beginning of the line */
473 curline = sel;
474 while (curline > start && ! ISRETURNCHAR(s[curline - 1])) curline--;
475 if (curline == start) return;
477 /* find unmatched paren */
478 parcount = 0;
479 inquote = FALSE;
480 pos = curline;
481 while (parcount >= 0 && --pos >= start) {
482 ch = s[pos];
483 switch (ch) {
484 case ')': if (! inquote) parcount++; break;
485 case '(': if (! inquote) parcount--; break;
486 case '"': inquote = ! inquote; break;
489 if (parcount == 0) return;
491 /* find beginning of the line containing the expression start */
492 lastline = pos;
493 while (lastline > 0 && ! ISRETURNCHAR(s[lastline - 1])) lastline--;
495 /* skip forward an s-expression or to first non blank */
496 pos += num_to_skip(s + pos, curline - pos);
498 if (pos > curline) pos = curline;
499 nblanks = pos - lastline;
501 /* adjust for the number of blanks already present, replace tabs by blanks */
502 for (pos = curline;
503 pos < length && (s[pos] == ' ' || s[pos] == '\t');
504 nblanks--, pos++)
505 if (s[pos] == '\t') s[pos] = ' ';
507 UnlockText();
509 /* insert or delete the appropriate number of blanks */
510 if (nblanks == 0) return;
512 sel += nblanks;
513 if (pos > length) pos = length;
514 Edit_SetSel(hWnd, pos, pos);
515 fix_blanks(hWnd, nblanks, curline);
516 length = Edit_GetTextLength(hWnd);
517 if (pos > length) pos = length;
518 Edit_SetSel(hWnd, sel, sel);
521 static void fix_blanks(HWND hWnd, int nblanks, int curline)
523 int i;
525 if (nblanks > 0) {
526 for (i = 0; i < nblanks && i < BUFSIZE -3; i++) obuf[i] = ' ';
527 obuf[i] = '\0';
528 Edit_SetSel(hWnd, curline, curline);
529 Edit_ReplaceSel(hWnd, obuf);
531 else {
532 obuf[0] = '\0';
533 Edit_SetSel(hWnd, curline, curline - nblanks);
534 Edit_ReplaceSel(hWnd, obuf);
538 static int num_to_skip(char *s, int n)
540 char str[4];
541 int i, pos, oldpos;
543 pos = 0;
545 if (n <= 0) pos = 0;
546 else if (*s == '(') {
548 s++; n--; pos = 1;
550 /* skip blanks */
551 while (n > 0 && (*s == ' ' || *s == '\t')) { s++; n--; pos++; }
553 /* check for end of line or list or lisp comment*/
554 if (n > 0 && ! ISRETURNCHAR(*s) && *s != ';' && *s != '(') {
556 /* check for special symbols */
557 for (i = 0; i < 3 && i < n; i++)
558 str[i] = islower(s[i]) ? (char) toupper(s[i]) : s[i];
559 str[i] = '\0';
560 if (is_special(s, n) /* strcmp(str, "DEF") == 0 || strcmp(str, "LET") == 0
561 || strcmp(str, "FLE") == 0 */ )
562 pos = 2;
563 else {
565 /* skip over the s-expression */
566 oldpos = pos;
567 while (n > 0 && *s != ' ' && *s != '\t' && ! ISRETURNCHAR(*s))
568 { s++; n--; pos++; }
570 /* check for another s expression */
571 for (i = 0; n > 0 && (*s == ' ' || *s == '\t'); s++, n--, i++) ;
572 if (n == 0 || ISRETURNCHAR(*s))
573 pos = (oldpos == pos) ? oldpos + 1 : oldpos;
574 else pos += i;
578 else {
580 /* skip over any blanks */
581 for (i = 0; n > 0 && (*s == ' ' || *s == '\t'); s++, n--, i++) ;
582 if (n > 0 && ! ISRETURNCHAR(*s)) pos += i;
584 return(pos);
587 static BOOL is_special(char *s, int n)
589 char str[10];
590 int i;
592 for (i = 0; i < n && i < 9; i++)
593 str[i] = islower(s[i]) ? (char) toupper(s[i]) : s[i];
594 str[i] = '\0';
596 if (n >= 5 && strncmp(str, "DEFUN", 5) == 0) return(TRUE);
597 if (n >= 8 && strncmp(str, "DEFMACRO", 8) == 0) return(TRUE);
598 if (n >= 7 && strncmp(str, "DEFMETH", 7) == 0) return(TRUE);
599 if (n >= 8 && strncmp(str, "DEFPROTO", 8) == 0) return(TRUE);
600 if (n >= 3 && strncmp(str, "LET", 3) == 0) return(TRUE);
601 if (n >= 4 && strncmp(str, "FLET", 4) == 0) return(TRUE);
602 if (n >= 4 && strncmp(str, "COND", 4) == 0) return(TRUE);
603 if (n >= 4 && strncmp(str, "CASE", 4) == 0) return(TRUE);
604 if (n >= 6 && strncmp(str, "LABELS", 6) == 0) return(TRUE);
605 if (n >= 6 && strncmp(str, "LAMBDA", 6) == 0) return(TRUE);
606 return(FALSE);
609 #ifndef NOTTY
610 static void check_trim_buffer(int maxonly)
612 int length, start;
613 UINT selstart, selend;
615 length = Edit_GetTextLength(hTTYWnd);
617 if (length > (maxonly ? MAXTTYBUF : TRIMTTYTO)) {
618 start = length - TRIMTTYTO;
619 edit_getsel(hTTYWnd, &selstart, &selend);
620 obuf[0] = '\0';
621 Edit_SetSel(hTTYWnd, 0, start);
622 Edit_ReplaceSel(hTTYWnd, obuf);
623 instart -= start;
624 inend -= start;
625 selstart -= start;
626 selend -= start;
627 Edit_SetSel(hTTYWnd, selstart, selend);
630 #endif /* NOTTY */
632 static void edit_getsel(HWND hWnd, UINT *pstart, UINT *pend)
634 long sel;
636 /**** this doesn't make sense for edit records larget than 64K */
637 sel = Edit_GetSel(hWnd);
638 if (pstart != NULL) *pstart = LOWORD(sel);
639 if (pend != NULL) *pend = HIWORD(sel);
642 #define TBUFSIZ 31000
643 static char *tbuf = NULL;
645 static char *LockText(void)
647 size_t length;
649 if (tbuf == NULL) {
650 HANDLE h;
651 h = GlobalAlloc(GMEM_MOVEABLE, TBUFSIZ + 1);
652 tbuf = h ? GlobalLock(h) : 0;
653 if (tbuf == NULL) return NULL;
656 length = Edit_GetTextLength(hTTYWnd);
657 GetWindowText(hTTYWnd, tbuf, TBUFSIZ);
658 tbuf[length] = '\0';
659 return tbuf;
662 static void UnlockText(void) {}