1/* vi:set ts=8 sw=8:
2 *
3 * VIM - Vi IMproved	by Bram Moolenaar
4 *			Visual Workshop integration by Gordon Prieur
5 *
6 * Do ":help uganda"  in Vim to read copying and usage conditions.
7 * Do ":help credits" in Vim to see a list of people who contributed.
8 * See README.txt for an overview of the Vim source code.
9 */
10
11/*
12 * Integration with Sun Workshop.
13 *
14 * This file should not change much, it's also used by other editors that
15 * connect to Workshop.  Consider changing workshop.c instead.
16 */
17/*
18-> consider using MakeSelectionVisible instead of gotoLine hacks
19   to show the line properly
20     -> consider using glue instead of our own message wrapping functions
21	(but can only use glue if we don't have to distribute source)
22*/
23
24#include "vim.h"
25
26#include <stdio.h>
27#include <stdlib.h>
28
29#ifdef INET_SOCKETS
30#include <netdb.h>
31#include <netinet/in.h>
32#else
33#include <sys/un.h>
34#endif
35
36#include <errno.h>
37#include <sys/types.h>
38#include <sys/socket.h>
39#include <sys/param.h>
40#ifdef HAVE_LIBGEN_H
41# include <libgen.h>
42#endif
43#include <unistd.h>
44#include <string.h>
45
46#include <X11/Intrinsic.h>
47#include <Xm/Xm.h>
48#include <Xm/AtomMgr.h>
49#include <Xm/PushB.h>
50
51#ifdef HAVE_X11_XPM_H
52# include <X11/xpm.h>
53#else
54# ifdef HAVE_XM_XPMP_H
55#  include <Xm/XpmP.h>
56# endif
57#endif
58
59#ifdef HAVE_UTIL_DEBUG_H
60# include <util/debug.h>
61#endif
62#ifdef HAVE_UTIL_MSGI18N_H
63# include <util/msgi18n.h>
64#endif
65
66#include "integration.h"	/* <EditPlugin/integration.h> */
67#ifdef HAVE_FRAME_H
68# include <frame.h>
69#endif
70
71#ifndef MAX
72# define MAX(a, b)    (a) > (b) ? (a) : (b)
73#endif
74
75#ifndef NOCATGETS
76# define NOCATGETS(x) x
77#endif
78
79/* Functions private to this file */
80static void workshop_connection_closed(void);
81static void messageFromEserve(XtPointer clientData, int *dum1, XtInputId *dum2);
82static void workshop_disconnect(void);
83static void workshop_sensitivity(int num, char *table);
84static void adjust_sign_name(char *filename);
85static void process_menuItem(char *);
86static void process_toolbarButton(char *);
87static void workshop_set_option_first(char *name, char *value);
88
89static size_t dummy;  /* to ignore return value of write() */
90
91#define CMDBUFSIZ	2048
92
93#ifdef DEBUG
94static FILE *dfd;
95static void pldebug(char *, ...);
96static void unrecognised_message(char *);
97
98#define HANDLE_ERRORS(cmd)	else unrecognised_message(cmd);
99#else
100#define HANDLE_ERRORS(cmd)
101#endif
102
103/*
104 * Version number of the protocol between an editor and eserve.
105 * This number should be incremented when the protocol
106 * is changed.
107 */
108#define	PROTOCOL_VERSION	"4.0.0"
109
110static int sd = -1;
111static XtInputId inputHandler;		/* Cookie for input */
112
113Boolean save_files = True;		/* When true, save all files before build actions */
114
115void
116workshop_connection_closed(void)
117{
118	/*
119	 * socket closed on other end
120	 */
121	XtRemoveInput(inputHandler);
122	inputHandler = 0;
123	sd = -1;
124}
125
126	static char *
127getCommand(void)
128{
129	int	 len;		/* length of this command */
130	char	 lenbuf[7];	/* get the length string here */
131	char	*newcb;		/* used to realloc cmdbuf */
132	static char	*cmdbuf;/* get the command string here */
133	static int	 cbsize;/* size of cmdbuf */
134
135	if ((len = read(sd, &lenbuf, 6)) == 6) {
136		lenbuf[6] = 0; /* Terminate buffer such that atoi() works right */
137		len = atoi(lenbuf);
138		if (cbsize < (len + 1)) {
139			newcb = (char *) realloc(cmdbuf,
140			    MAX((len + 256), CMDBUFSIZ));
141			if (newcb != NULL) {
142				cmdbuf = newcb;
143				cbsize = MAX((len + 256), CMDBUFSIZ);
144			}
145		}
146		if (cbsize >= len && (len = read(sd, cmdbuf, len)) > 0) {
147			cmdbuf[len] = 0;
148			return cmdbuf;
149		} else {
150			return NULL;
151		}
152	} else {
153		if (len == 0) { /* EOF */
154			workshop_connection_closed();
155		}
156		return NULL;
157	}
158
159}
160
161void
162messageFromEserve(XtPointer clientData UNUSED,
163		  int *dum1 UNUSED,
164		  XtInputId *dum2 UNUSED)
165{
166	char	*cmd;		/* the 1st word of the command */
167
168	cmd = getCommand();
169	if (cmd == NULL) {
170		/* We're being shut down by eserve and the "quit" message
171		 * didn't arrive before the socket connection got closed */
172		return;
173	}
174#ifdef DEBUG
175	pldebug("%s\n", cmd);
176#endif
177	switch (*cmd) {
178	case 'a':
179		if (cmd[1] == 'c' &&
180		    strncmp(cmd, NOCATGETS("ack "), 4) == 0) {
181			int ackNum;
182			char buf[20];
183
184			ackNum = atoi(&cmd[4]);
185			vim_snprintf(buf, sizeof(buf),
186					       NOCATGETS("ack %d\n"), ackNum);
187			dummy = write(sd, buf, strlen(buf));
188		} else if (strncmp(cmd,
189		    NOCATGETS("addMarkType "), 12) == 0) {
190			int idx;
191			char *color;
192			char *sign;
193
194			idx = atoi(strtok(&cmd[12], " "));
195			color  = strtok(NULL, NOCATGETS("\001"));
196			sign  = strtok(NULL, NOCATGETS("\001"));
197			/* Skip space that separates names */
198			if (color) {
199				color++;
200			}
201			if (sign) {
202				sign++;
203			}
204			/* Change sign name to accommodate a different size? */
205			adjust_sign_name(sign);
206			workshop_add_mark_type(idx, color, sign);
207		}
208		HANDLE_ERRORS(cmd);
209		break;
210
211	case 'b':
212		if (strncmp(cmd,
213		    NOCATGETS("balloon "), 8) == 0) {
214			char *tip;
215
216			tip  = strtok(&cmd[8], NOCATGETS("\001"));
217			workshop_show_balloon_tip(tip);
218		}
219		HANDLE_ERRORS(cmd);
220		break;
221
222	case 'c':
223		if (strncmp(cmd,
224		    NOCATGETS("changeMarkType "), 15) == 0) {
225			char *file;
226			int markId;
227			int type;
228
229			file  = strtok(&cmd[15], " ");
230			markId = atoi(strtok(NULL, " "));
231			type = atoi(strtok(NULL, " "));
232			workshop_change_mark_type(file, markId, type);
233		}
234		HANDLE_ERRORS(cmd);
235		break;
236
237	case 'd':
238		if (strncmp(cmd, NOCATGETS("deleteMark "), 11) == 0) {
239			char *file;
240			int markId;
241
242			file  = strtok(&cmd[11], " ");
243			markId = atoi(strtok(NULL, " "));
244			workshop_delete_mark(file, markId);
245		}
246		HANDLE_ERRORS(cmd);
247		break;
248
249	case 'f':
250		if (cmd[1] == 'o' &&
251		    strncmp(cmd, NOCATGETS("footerMsg "), 10) == 0) {
252			int severity;
253			char *message;
254
255			severity =
256			    atoi(strtok(&cmd[10], " "));
257			message = strtok(NULL, NOCATGETS("\001"));
258
259			workshop_footer_message(message, severity);
260		} else if (strncmp(cmd,
261		    NOCATGETS("frontFile "), 10) == 0) {
262			char *file;
263
264			file  = strtok(&cmd[10], " ");
265			workshop_front_file(file);
266		}
267		HANDLE_ERRORS(cmd);
268		break;
269
270	case 'g':
271		if (cmd[1] == 'e' &&
272		    strncmp(cmd, NOCATGETS("getMarkLine "), 12) == 0) {
273			char *file;
274			int markid;
275			int line;
276			char buf[100];
277
278			file  = strtok(&cmd[12], " ");
279			markid = atoi(strtok(NULL, " "));
280			line = workshop_get_mark_lineno(file, markid);
281			vim_snprintf(buf, sizeof(buf),
282					     NOCATGETS("markLine %s %d %d\n"),
283			    file, markid, line);
284			dummy = write(sd, buf, strlen(buf));
285		} else if (cmd[1] == 'o' && cmd[4] == 'L' &&
286		    strncmp(cmd, NOCATGETS("gotoLine "), 9) == 0) {
287			char *file;
288			int lineno;
289
290			file  = strtok(&cmd[9], " ");
291			lineno = atoi(strtok(NULL, " "));
292			workshop_goto_line(file, lineno);
293		} else if (strncmp(cmd,
294		    NOCATGETS("gotoMark "), 9) == 0) {
295			char *file;
296			int markId;
297			char *message;
298
299			file  = strtok(&cmd[9], " ");
300			markId = atoi(strtok(NULL, " "));
301			message = strtok(NULL, NOCATGETS("\001"));
302			workshop_goto_mark(file, markId, message);
303#ifdef NOHANDS_SUPPORT_FUNCTIONS
304		} else if (strcmp(cmd, NOCATGETS("getCurrentFile")) == 0) {
305			char *f = workshop_test_getcurrentfile();
306			char buffer[2*MAXPATHLEN];
307			vim_snprintf(buffer, sizeof(buffer),
308					NOCATGETS("currentFile %d %s"),
309				f ? strlen(f) : 0, f ? f : "");
310			workshop_send_message(buffer);
311		} else if (strcmp(cmd, NOCATGETS("getCursorRow")) == 0) {
312			int row = workshop_test_getcursorrow();
313			char buffer[2*MAXPATHLEN];
314			vim_snprintf(buffer, sizeof(buffer),
315					NOCATGETS("cursorRow %d"), row);
316			workshop_send_message(buffer);
317		} else if (strcmp(cmd, NOCATGETS("getCursorCol")) == 0) {
318			int col = workshop_test_getcursorcol();
319			char buffer[2*MAXPATHLEN];
320			vim_snprintf(buffer, sizeof(buffer),
321					NOCATGETS("cursorCol %d"), col);
322			workshop_send_message(buffer);
323		} else if (strcmp(cmd, NOCATGETS("getCursorRowText")) == 0) {
324			char *t = workshop_test_getcursorrowtext();
325			char buffer[2*MAXPATHLEN];
326			vim_snprintf(buffer, sizeof(buffer),
327					NOCATGETS("cursorRowText %d %s"),
328				t ? strlen(t) : 0, t ? t : "");
329			workshop_send_message(buffer);
330		} else if (strcmp(cmd, NOCATGETS("getSelectedText")) == 0) {
331			char *t = workshop_test_getselectedtext();
332			char buffer[2*MAXPATHLEN];
333			vim_snprintf(buffer, sizeof(buffer),
334					NOCATGETS("selectedText %d %s"),
335				t ? strlen(t) : 0, t ? t : "");
336			workshop_send_message(buffer);
337#endif
338		}
339		HANDLE_ERRORS(cmd);
340		break;
341
342	case 'l':
343		if (strncmp(cmd, NOCATGETS("loadFile "), 9) == 0) {
344			char *file;
345			int line;
346			char *frameid;
347
348			file  = strtok(&cmd[9], " ");
349			line = atoi(strtok(NULL, " "));
350			frameid = strtok(NULL, " ");
351			workshop_load_file(file, line, frameid);
352		}
353		HANDLE_ERRORS(cmd);
354		break;
355
356	case 'm':			/* Menu, minimize, maximize */
357		if (cmd[1] == 'e' && cmd[4] == 'B' &&
358		    strncmp(cmd, NOCATGETS("menuBegin "), 10) == 0) {
359			workshop_menu_begin(&cmd[10]);
360		} else if (cmd[1] == 'e' && cmd[4] == 'I' &&
361		    strncmp(cmd, NOCATGETS("menuItem "), 9) == 0) {
362			process_menuItem(cmd);
363		} else if (cmd[1] == 'e' && cmd[4] == 'E' &&
364		    strcmp(cmd, NOCATGETS("menuEnd")) == 0) {
365			workshop_menu_end();
366		} else if (cmd[1] == 'a' &&
367		    strcmp(cmd, NOCATGETS("maximize")) == 0) {
368			workshop_maximize();
369		} else if (strcmp(cmd, NOCATGETS("minimize")) == 0) {
370			workshop_minimize();
371		}
372		HANDLE_ERRORS(cmd);
373		break;
374
375	case 'o':
376		if (cmd[1] == 'p' &&
377		    strcmp(cmd, NOCATGETS("option"))) {
378			char *name;
379			char *value;
380
381			name  = strtok(&cmd[7], " ");
382			value = strtok(NULL, " ");
383			workshop_set_option_first(name, value);
384		}
385		HANDLE_ERRORS(cmd);
386		break;
387
388	case 'p':
389		if (strcmp(cmd, NOCATGETS("ping")) == 0) {
390#if 0
391			int pingNum;
392
393			pingNum = atoi(&cmd[5]);
394			workshop_send_ack(ackNum);
395			/* WHAT DO I DO HERE? */
396#endif
397		}
398		HANDLE_ERRORS(cmd);
399		break;
400
401	case 'q':
402		if (strncmp(cmd, NOCATGETS("quit"), 4) == 0) {
403
404			/* Close the connection. It's important to do
405			 * that now, since workshop_quit might be
406			 * looking at open files.  For example, if you
407			 * have modified one of the files without
408			 * saving, NEdit will ask you what you want to
409			 * do, and spin loop by calling
410			 * XtAppProcessEvent while waiting for your
411			 * reply. In this case, if we still have an
412			 * input handler and the socket has been
413			 * closed on the other side when eserve
414			 * expired, we will hang in IoWait.
415			 */
416			workshop_disconnect();
417
418			workshop_quit();
419		}
420		HANDLE_ERRORS(cmd);
421		break;
422
423	case 'r':
424		if (cmd[1] == 'e' &&
425		    strncmp(cmd, NOCATGETS("reloadFile "), 11) == 0) {
426			char *file;
427			int line;
428
429			file  = strtok(&cmd[11], " ");
430			line = atoi(strtok(NULL, " "));
431			workshop_reload_file(file, line);
432		}
433		HANDLE_ERRORS(cmd);
434		break;
435
436	case 's':
437		if (cmd[1] == 'e' && cmd[2] == 't' &&
438		    strncmp(cmd, NOCATGETS("setMark "), 8) == 0) {
439			char *file;
440			int line;
441			int markId;
442			int type;
443
444			file  = strtok(&cmd[8], " ");
445			line = atoi(strtok(NULL, " "));
446			markId = atoi(strtok(NULL, " "));
447			type = atoi(strtok(NULL, " "));
448			workshop_set_mark(file, line, markId, type);
449		} else if (cmd[1] == 'h' &&
450		    strncmp(cmd, NOCATGETS("showFile "), 9) == 0) {
451			workshop_show_file(&cmd[9]);
452		} else if (cmd[1] == 'u' &&
453		    strncmp(cmd, NOCATGETS("subMenu "), 8) == 0) {
454			char *label;
455
456			label  = strtok(&cmd[8], NOCATGETS("\001"));
457			workshop_submenu_begin(label);
458		} else if (cmd[1] == 'u' &&
459		    strcmp(cmd, NOCATGETS("subMenuEnd")) == 0) {
460			workshop_submenu_end();
461		} else if (cmd[1] == 'e' && cmd[2] == 'n' &&
462		    strncmp(cmd, NOCATGETS("sensitivity "), 12) == 0) {
463			int num;
464			char *bracket;
465			char *table;
466
467			num = atoi(strtok(&cmd[12], " "));
468			bracket = strtok(NULL, " ");
469			if (*bracket != '[') {
470				fprintf(stderr, NOCATGETS("Parsing "
471				    "error for sensitivity\n"));
472			} else {
473				table = strtok(NULL, NOCATGETS("]"));
474				workshop_sensitivity(num, table);
475			}
476		} else if (cmd[1] == 'e' && cmd[2] == 'n' && cmd[3] == 'd' &&
477			   strncmp(cmd, NOCATGETS("sendVerb "), 9) == 0) {
478			/* Send the given verb back (used for the
479			 * debug.lineno callback (such that other tools
480			 * can obtain the position coordinates or the
481			 * selection) */
482			char *verb;
483
484			verb = strtok(&cmd[9], " ");
485			workshop_perform_verb(verb, NULL);
486		} else if (cmd[1] == 'a' &&
487		    strncmp(cmd, NOCATGETS("saveFile "), 9) == 0) {
488			workshop_save_file(&cmd[9]);
489#ifdef NOHANDS_SUPPORT_FUNCTIONS
490		} else if (strncmp(cmd, NOCATGETS("saveSensitivity "), 16) == 0) {
491			char *file;
492
493			file  = strtok(&cmd[16], " ");
494			workshop_save_sensitivity(file);
495#endif
496		}
497		HANDLE_ERRORS(cmd);
498		break;
499
500	case 't':			/* Toolbar */
501		if (cmd[8] == 'e' &&
502		    strncmp(cmd, NOCATGETS("toolbarBegin"), 12) == 0) {
503			workshop_toolbar_begin();
504		} else if (cmd[8] == 'u' &&
505		    strncmp(cmd, NOCATGETS("toolbarButton"), 13) == 0) {
506			process_toolbarButton(cmd);
507		} else if (cmd[7] == 'E' &&
508		    strcmp(cmd, NOCATGETS("toolbarEnd")) == 0) {
509			workshop_toolbar_end();
510		}
511		HANDLE_ERRORS(cmd);
512		break;
513
514#ifdef DEBUG
515	default:
516		unrecognised_message(cmd);
517		break;
518#endif
519	}
520}
521
522static void
523process_menuItem(
524	char	*cmd)
525{
526	char *label  = strtok(&cmd[9], NOCATGETS("\001"));
527	char *verb  = strtok(NULL, NOCATGETS("\001"));
528	char *acc = strtok(NULL, NOCATGETS("\001"));
529	char *accText  = strtok(NULL, NOCATGETS("\001"));
530	char *name  = strtok(NULL, NOCATGETS("\001"));
531	char *sense  = strtok(NULL, NOCATGETS("\n"));
532	char *filepos  = strtok(NULL, NOCATGETS("\n"));
533	if (*acc == '-') {
534		acc = NULL;
535	}
536	if (*accText == '-') {
537		accText = NULL;
538	}
539	workshop_menu_item(label, verb, acc, accText, name, filepos, sense);
540
541}
542
543
544static void
545process_toolbarButton(
546	char	*cmd)			/* button definition */
547{
548	char *label  = strtok(&cmd[14], NOCATGETS("\001"));
549	char *verb  = strtok(NULL, NOCATGETS("\001"));
550	char *senseVerb  = strtok(NULL, NOCATGETS("\001"));
551	char *filepos  = strtok(NULL, NOCATGETS("\001"));
552	char *help  = strtok(NULL, NOCATGETS("\001"));
553	char *sense  = strtok(NULL, NOCATGETS("\001"));
554	char *file  = strtok(NULL, NOCATGETS("\001"));
555	char *left  = strtok(NULL, NOCATGETS("\n"));
556
557	if (!strcmp(label, NOCATGETS("-"))) {
558		label = NULL;
559	}
560	if (!strcmp(help, NOCATGETS("-"))) {
561		help = NULL;
562	}
563	if (!strcmp(file, NOCATGETS("-"))) {
564		file = NULL;
565	}
566	if (!strcmp(senseVerb, NOCATGETS("-"))) {
567		senseVerb = NULL;
568	}
569	workshop_toolbar_button(label, verb, senseVerb, filepos, help,
570				sense, file, left);
571}
572
573
574#ifdef DEBUG
575void
576unrecognised_message(
577	char	*cmd)
578{
579	pldebug("Unrecognised eserve message:\n\t%s\n", cmd);
580	/* abort(); */
581}
582#endif
583
584
585/* Change sign name to accommodate a different size:
586 * Create the filename based on the height. The filename format
587 * of multisize icons are:
588 *    x.xpm   : largest icon
589 *    x1.xpm  : smaller icon
590 *    x2.xpm  : smallest icon */
591	void
592adjust_sign_name(char *filename)
593{
594	char *s;
595	static int fontSize = -1;
596
597	if (fontSize == -1)
598		fontSize = workshop_get_font_height();
599	if (fontSize == 0)
600		return;
601	if (filename[0] == '-')
602		return;
603
604	/* This is ugly: later we should instead pass the fontheight over
605	 * to eserve on startup and let eserve just send the right filenames
606	 * to us in the first place
607
608	 * I know that the filename will end with 1.xpm (see
609	 * GuiEditor.cc`LispPrintSign if you wonder why) */
610	s = filename+strlen(filename)-5;
611	if (fontSize <= 11)
612		strcpy(s, "2.xpm");
613	else if (fontSize <= 15)
614		strcpy(s, "1.xpm");
615	else
616		strcpy(s, ".xpm");
617}
618
619#if 0
620/* Were we invoked by WorkShop? This function can be used early during startup
621   if you want to do things differently if the editor is started standalone
622   or in WorkShop mode. For example, in standalone mode you may not want to
623   add a footer/message area or a sign gutter. */
624int
625workshop_invoked()
626{
627	static int result = -1;
628	if (result == -1) {
629		result = (getenv(NOCATGETS("SPRO_EDITOR_SOCKET")) != NULL);
630	}
631	return result;
632}
633#endif
634
635/* Connect back to eserve */
636void	workshop_connect(XtAppContext context)
637{
638#ifdef INET_SOCKETS
639	struct sockaddr_in	server;
640	struct hostent *	host;
641	int			port;
642#else
643	struct sockaddr_un	server;
644#endif
645	char			buf[32];
646	char *			address;
647#ifdef DEBUG
648	char			*file;
649#endif
650
651	address = getenv(NOCATGETS("SPRO_EDITOR_SOCKET"));
652	if (address == NULL) {
653		return;
654	}
655
656#ifdef INET_SOCKETS
657	port = atoi(address);
658
659	if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
660		PERROR(NOCATGETS("workshop_connect"));
661		return;
662	}
663
664	/* Get the server internet address and put into addr structure */
665	/* fill in the socket address structure and connect to server */
666	vim_memset((char *)&server, '\0', sizeof(server));
667	server.sin_family = AF_INET;
668	server.sin_port = port;
669	if ((host = gethostbyname(NOCATGETS("localhost"))) == NULL) {
670		PERROR(NOCATGETS("gethostbyname"));
671		sd = -1;
672		return;
673	}
674	memcpy((char *)&server.sin_addr, host->h_addr, host->h_length);
675#else
676	if ((sd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
677		PERROR(NOCATGETS("workshop_connect"));
678		return;
679	}
680
681	server.sun_family = AF_UNIX;
682	strcpy(server.sun_path, address);
683#endif
684	/* Connect to server */
685	if (connect(sd, (struct sockaddr *)&server, sizeof(server))) {
686		if (errno == ECONNREFUSED) {
687			close(sd);
688#ifdef INET_SOCKETS
689			if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
690				PERROR(NOCATGETS("workshop_connect"));
691				return;
692			}
693#else
694			if ((sd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
695				PERROR(NOCATGETS("workshop_connect"));
696				return;
697			}
698#endif
699			if (connect(sd, (struct sockaddr *)&server,
700						sizeof(server))) {
701				PERROR(NOCATGETS("workshop_connect"));
702				return;
703			}
704
705		} else {
706			PERROR(NOCATGETS("workshop_connect"));
707			return;
708		}
709	}
710
711	/* tell notifier we are interested in being called
712	 * when there is input on the editor connection socket
713	 */
714	inputHandler = XtAppAddInput(context, sd, (XtPointer) XtInputReadMask,
715				     messageFromEserve, NULL);
716#ifdef DEBUG
717	if ((file = getenv(NOCATGETS("SPRO_PLUGIN_DEBUG"))) != NULL) {
718		char buf[BUFSIZ];
719
720		unlink(file);
721		vim_snprintf(buf, sizeof(buf), "date > %s", file);
722		system(buf);
723		dfd = fopen(file, "a");
724	} else {
725		dfd = NULL;
726	}
727#endif
728
729	vim_snprintf(buf, sizeof(buf), NOCATGETS("connected %s %s %s\n"),
730		workshop_get_editor_name(),
731		PROTOCOL_VERSION,
732		workshop_get_editor_version());
733	dummy = write(sd, buf, strlen(buf));
734
735	vim_snprintf(buf, sizeof(buf), NOCATGETS("ack 1\n"));
736	dummy = write(sd, buf, strlen(buf));
737}
738
739void	workshop_disconnect()
740{
741	/* Probably need to send some message here */
742
743	/*
744	 * socket closed on other end
745	 */
746	XtRemoveInput(inputHandler);
747	close(sd);
748	inputHandler = 0;
749	sd = -1;
750
751}
752
753/*
754 * Utility functions
755 */
756
757
758/* Minimize and maximize shells. From libutil's shell.cc. */
759
760/* utility functions from libutil's shell.cc */
761static Boolean
762isWindowMapped(Display *display, Window win)
763{
764	XWindowAttributes winAttrs;
765	XGetWindowAttributes(display,
766			     win,
767			     &winAttrs);
768	if (winAttrs.map_state == IsViewable) {
769		return(True);
770	} else {
771		return(False);
772	}
773}
774
775static Boolean
776isMapped(Widget widget)
777{
778	if (widget == NULL) {
779		return(False);
780	}
781
782	if (XtIsRealized(widget) == False) {
783		return(False);
784	}
785
786	return(isWindowMapped(XtDisplay(widget), XtWindow(widget)));
787}
788
789static Boolean
790widgetIsIconified(
791	Widget		 w)
792{
793	Atom		 wm_state;
794	Atom		 act_type;		/* actual Atom type returned */
795	int		 act_fmt;		/* actual format returned */
796	u_long		 nitems_ret;		/* number of items returned */
797	u_long		 bytes_after;		/* number of bytes remaining */
798	u_long		*property;		/* actual property returned */
799
800	/*
801	 * If a window is iconified its WM_STATE is set to IconicState. See
802	 * ICCCM Version 2.0, section 4.1.3.1 for more details.
803	 */
804
805	wm_state = XmInternAtom(XtDisplay(w), NOCATGETS("WM_STATE"), False);
806	if (XtWindow(w) != 0) {			/* only check if window exists! */
807		XGetWindowProperty(XtDisplay(w), XtWindow(w), wm_state, 0L, 2L,
808		    False, AnyPropertyType, &act_type, &act_fmt, &nitems_ret,
809		    &bytes_after, (u_char **) &property);
810		if (nitems_ret == 2 && property[0] == IconicState) {
811			return True;
812		}
813	}
814
815	return False;
816
817}    /* end widgetIsIconified */
818
819void
820workshop_minimize_shell(Widget shell)
821{
822	if (shell != NULL &&
823	    XtIsObject(shell) &&
824	    XtIsRealized(shell) == True) {
825		if (isMapped(shell) == True) {
826			XIconifyWindow(XtDisplay(shell), XtWindow(shell),
827			       XScreenNumberOfScreen(XtScreen(shell)));
828		}
829		XtVaSetValues(shell,
830			      XmNiconic, True,
831			      NULL);
832	}
833}
834
835void workshop_maximize_shell(Widget shell)
836{
837	if (shell != NULL &&
838	    XtIsRealized(shell) == True &&
839	    widgetIsIconified(shell) == True &&
840	    isMapped(shell) == False) {
841		XtMapWidget(shell);
842		/* This used to be
843		     XtPopdown(shell);
844		     XtPopup(shell, XtGrabNone);
845		   However, I found that that would drop any transient
846		   windows that had been iconified with the window.
847		   According to the ICCCM, XtMapWidget should be used
848		   to bring a window from Iconic to Normal state.
849		   However, Rich Mauri did a lot of work on this during
850		   Bart, and found that XtPopDown,XtPopup was required
851		   to fix several bugs involving multiple CDE workspaces.
852		   I've tested it now and things seem to work fine but
853		   I'm leaving this note for history in case this needs
854		   to be revisited.
855		*/
856	}
857}
858
859
860Boolean workshop_get_width_height(int *width, int *height)
861{
862	static int	wid = 0;
863	static int	hgt = 0;
864	static Boolean	firstTime = True;
865	static Boolean	success = False;
866
867	if (firstTime) {
868		char	*settings;
869
870		settings = getenv(NOCATGETS("SPRO_GUI_WIDTH_HEIGHT"));
871		if (settings != NULL) {
872			wid = atoi(settings);
873			settings = strrchr(settings, ':');
874			if (settings++ != NULL) {
875				hgt = atoi(settings);
876			}
877			if (wid > 0 && hgt > 0) {
878				success = True;
879			}
880			firstTime = False;
881		}
882	}
883
884	if (success) {
885		*width = wid;
886		*height = hgt;
887	}
888	return success;
889}
890
891/*
892 * Toolbar code
893 */
894
895void workshop_sensitivity(int num, char *table)
896{
897	/* build up a verb table */
898	VerbSense *vs;
899	int i;
900	char *s;
901	if ((num < 1) || (num > 500)) {
902		return;
903	}
904
905	vs = (VerbSense *)malloc((num+1)*sizeof(VerbSense));
906
907	/* Point to the individual names (destroys the table string, but
908	 * that's okay -- this is more efficient than duplicating strings) */
909	s = table;
910	for (i = 0; i < num; i++) {
911		while (*s == ' ') {
912			s++;
913		}
914		vs[i].verb = s;
915		while (*s && (*s != ' ') && (*s != '\001')) {
916			s++;
917		}
918		if (*s == 0) {
919			vs[i].verb = NULL;
920			break;
921		}
922		if (*s == '\001') {
923			*s = 0;
924			s++;
925		}
926		*s = 0;
927		s++;
928		while (*s == ' ') {
929			s++;
930		}
931		if (*s == '1') {
932			vs[i].sense = 1;
933		} else {
934			vs[i].sense = 0;
935		}
936		s++;
937	}
938	vs[i].verb = NULL;
939
940	workshop_frame_sensitivities(vs);
941
942	free(vs);
943}
944
945/*
946 * Options code
947 */
948/* Set an editor option.
949 * IGNORE an option if you do not recognize it.
950 */
951void workshop_set_option_first(char *name, char *value)
952{
953	/* Currently value can only be on/off. This may change later (for
954	 * example to set an option like "balloon evaluate delay", but
955	 * for now just convert it into a boolean */
956	Boolean on = !strcmp(value, "on");
957
958	if (!strcmp(name, "workshopkeys")) {
959		workshop_hotkeys(on);
960	} else if (!strcmp(name, "savefiles")) {
961		save_files = on;
962	} else if (!strcmp(name, "balloon")) {
963		workshop_balloon_mode(on);
964	} else if (!strcmp(name, "balloondelay")) {
965		int delay = atoi(value);
966		/* Should I validate the number here?? */
967		workshop_balloon_delay(delay);
968	} else {
969		/* Let editor interpret it */
970		workshop_set_option(name, value);
971	}
972}
973
974
975void workshop_file_closed_lineno(char *filename, int lineno)
976{
977	char buffer[2*MAXPATHLEN];
978	vim_snprintf(buffer, sizeof(buffer),
979			NOCATGETS("deletedFile %s %d\n"), filename, lineno);
980	dummy = write(sd, buffer, strlen(buffer));
981}
982
983void workshop_file_opened(char *filename, int readOnly)
984{
985	char buffer[2*MAXPATHLEN];
986	vim_snprintf(buffer, sizeof(buffer),
987			NOCATGETS("loadedFile %s %d\n"), filename, readOnly);
988	dummy = write(sd, buffer, strlen(buffer));
989}
990
991
992void workshop_file_saved(char *filename)
993{
994	char buffer[2*MAXPATHLEN];
995	vim_snprintf(buffer, sizeof(buffer),
996			NOCATGETS("savedFile %s\n"), filename);
997	dummy = write(sd, buffer, strlen(buffer));
998
999	/* Let editor report any moved marks that the eserve client
1000	 * should deal with (for example, moving location-based breakpoints) */
1001	workshop_moved_marks(filename);
1002}
1003
1004void workshop_frame_moved(int new_x, int new_y, int new_w, int new_h)
1005{
1006	char buffer[200];
1007
1008	if (sd >= 0)
1009	{
1010		vim_snprintf(buffer, sizeof(buffer),
1011				NOCATGETS("frameAt %d %d %d %d\n"),
1012				new_x, new_y, new_w, new_h);
1013		dummy = write(sd, buffer, strlen(buffer));
1014	}
1015}
1016
1017/* A button in the toolbar has been pushed.
1018 * Clientdata is a pointer used by the editor code to figure out the
1019 * positions for this toolbar (probably by storing a window pointer,
1020 * and then fetching the current buffer for that window and looking up
1021 * cursor and selection positions etc.) */
1022void workshop_perform_verb(char *verb, void *clientData)
1023{
1024	char *filename;
1025	int curLine;
1026	int curCol;
1027	int selStartLine;
1028	int selStartCol;
1029	int selEndLine;
1030	int selEndCol;
1031	int selLength;
1032	char *selection;
1033
1034	char buf[2*MAXPATHLEN];
1035/* Later: needsFilePos indicates whether or not we need to fetch all this
1036 * info for this verb... for now, however, it looks as if
1037 * eserve parsing routines depend on it always being present */
1038
1039	if (workshop_get_positions(clientData,
1040				   &filename,
1041				   &curLine,
1042				   &curCol,
1043				   &selStartLine,
1044				   &selStartCol,
1045				   &selEndLine,
1046				   &selEndCol,
1047				   &selLength,
1048				   &selection)) {
1049		if (selection == NULL) {
1050			selection = NOCATGETS("");
1051		}
1052
1053		/* Should I save the files??? This is currently done by checking
1054		   if the verb is one of a few recognized ones. Later we can pass
1055		   this list from eserve to the editor (it's currently hardcoded in
1056		   vi and emacs as well). */
1057		if (save_files) {
1058			if (!strcmp(verb, "build.build") || !strcmp(verb, "build.build-file") ||
1059			    !strcmp(verb, "debug.fix") || !strcmp(verb, "debug.fix-all")) {
1060				workshop_save_files();
1061			}
1062		}
1063
1064		vim_snprintf(buf, sizeof(buf),
1065			NOCATGETS("toolVerb %s %s %d,%d %d,%d %d,%d %d %s\n"),
1066			verb,
1067			filename,
1068			curLine, curCol,
1069			selStartLine, selStartCol,
1070			selEndLine, selEndCol,
1071			selLength,
1072			selection);
1073		dummy = write(sd, buf, strlen(buf));
1074		if (*selection) {
1075			free(selection);
1076		}
1077	}
1078}
1079
1080/* Send a message to eserve */
1081#if defined(NOHANDS_SUPPORT_FUNCTIONS) || defined(FEAT_BEVAL)
1082void workshop_send_message(char *buf)
1083{
1084	dummy = write(sd, buf, strlen(buf));
1085}
1086#endif
1087
1088/* Some methods, like currentFile, cursorPos, etc. are missing here.
1089 * But it looks like these are used for NoHands testing only so we
1090 * won't bother requiring editors to implement these
1091 */
1092
1093
1094#ifdef DEBUG
1095
1096void
1097pldebug(
1098	char		*fmt,	/* a printf style format line */
1099	...)
1100{
1101	va_list		 ap;
1102
1103	if (dfd != NULL) {
1104		va_start(ap, fmt);
1105		vfprintf(dfd, fmt, ap);
1106		va_end(ap);
1107		fflush(dfd);
1108	}
1109
1110}    /* end pldebug */
1111
1112#endif
1113