vim_client-ruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1039 @@
1
+ /* vi:set ts=8 sts=4 sw=4:
2
+ *
3
+ * VimClient by Brian D. Burns
4
+ * Ruby extension providing a client for Vim's 'clientserver' feature.
5
+ *
6
+ * Adapted from Vim's if_xcmdsrv.c (v7-3-555)
7
+ */
8
+ #include "vim_client.h"
9
+
10
+ /*
11
+ *
12
+ * VIM - Vi IMproved by Bram Moolenaar
13
+ * X command server by Flemming Madsen
14
+ *
15
+ * Do ":help uganda" in Vim to read copying and usage conditions.
16
+ * Do ":help credits" in Vim to see a list of people who contributed.
17
+ * See README.txt for an overview of the Vim source code.
18
+ *
19
+ * if_xcmdsrv.c: Functions for passing commands through an X11 display.
20
+ *
21
+ */
22
+
23
+ /*
24
+ * This file provides procedures that implement the command server
25
+ * functionality of Vim when in contact with an X11 server.
26
+ *
27
+ * Adapted from TCL/TK's send command in tkSend.c of the tk 3.6 distribution.
28
+ * Adapted for use in Vim by Flemming Madsen. Protocol changed to that of tk 4
29
+ */
30
+
31
+ /*
32
+ * Copyright (c) 1989-1993 The Regents of the University of California.
33
+ * All rights reserved.
34
+ *
35
+ * Permission is hereby granted, without written agreement and without
36
+ * license or royalty fees, to use, copy, modify, and distribute this
37
+ * software and its documentation for any purpose, provided that the
38
+ * above copyright notice and the following two paragraphs appear in
39
+ * all copies of this software.
40
+ *
41
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
42
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
43
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
44
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
45
+ *
46
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
47
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
48
+ * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
49
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
50
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
51
+ */
52
+
53
+ /*
54
+ * The information below is used for communication between processes
55
+ * during "send" commands. Each process keeps a private window, never
56
+ * even mapped, with one property, "Comm". When a command is sent to
57
+ * an interpreter, the command is appended to the comm property of the
58
+ * communication window associated with the interp's process. Similarly,
59
+ * when a result is returned from a sent command, it is also appended
60
+ * to the comm property.
61
+ *
62
+ * Each command and each result takes the form of ASCII text. For a
63
+ * command, the text consists of a nul character followed by several
64
+ * nul-terminated ASCII strings. The first string consists of a
65
+ * single letter:
66
+ * "c" for an expression
67
+ * "k" for keystrokes
68
+ * "r" for reply
69
+ * "n" for notification.
70
+ * Subsequent strings have the form "option value" where the following options
71
+ * are supported:
72
+ *
73
+ * -r commWindow serial
74
+ *
75
+ * This option means that a response should be sent to the window
76
+ * whose X identifier is "commWindow" (in hex), and the response should
77
+ * be identified with the serial number given by "serial" (in decimal).
78
+ * If this option isn't specified then the send is asynchronous and
79
+ * no response is sent.
80
+ *
81
+ * -n name
82
+ * "Name" gives the name of the application for which the command is
83
+ * intended. This option must be present.
84
+ *
85
+ * -E encoding
86
+ * Encoding name used for the text. This is the 'encoding' of the
87
+ * sender. The receiver may want to do conversion to his 'encoding'.
88
+ *
89
+ * -s script
90
+ * "Script" is the script to be executed. This option must be
91
+ * present. Taken as a series of keystrokes in a "k" command where
92
+ * <Key>'s are expanded
93
+ *
94
+ * The options may appear in any order. The -n and -s options must be
95
+ * present, but -r may be omitted for asynchronous RPCs. For compatibility
96
+ * with future releases that may add new features, there may be additional
97
+ * options present; as long as they start with a "-" character, they will
98
+ * be ignored.
99
+ *
100
+ * A result also consists of a zero character followed by several null-
101
+ * terminated ASCII strings. The first string consists of the single
102
+ * letter "r". Subsequent strings have the form "option value" where
103
+ * the following options are supported:
104
+ *
105
+ * -s serial
106
+ * Identifies the command for which this is the result. It is the
107
+ * same as the "serial" field from the -s option in the command. This
108
+ * option must be present.
109
+ *
110
+ * -r result
111
+ * "Result" is the result string for the script, which may be either
112
+ * a result or an error message. If this field is omitted then it
113
+ * defaults to an empty string.
114
+ *
115
+ * -c code
116
+ * 0: for OK. This is the default.
117
+ * 1: for error: Result is the last error
118
+ *
119
+ * -i errorInfo
120
+ * -e errorCode
121
+ * Not applicable for Vim
122
+ *
123
+ * Options may appear in any order, and only the -s option must be
124
+ * present. As with commands, there may be additional options besides
125
+ * these; unknown options are ignored.
126
+ */
127
+
128
+ /*
129
+ * Maximum size property that can be read at one time by this module:
130
+ * (length in 32-bit multiples of the data to be retrieved)
131
+ */
132
+ #define MAX_PROP_WORDS 100000
133
+
134
+ /*
135
+ * Structure used to process the result returned from the server.
136
+ */
137
+ typedef struct {
138
+ int serial; /* Serial number expected in result. */
139
+ int mode; /* asExpr or asKeys2 */
140
+ int code; /* Result Code. 0 is OK */
141
+ char_u *result; /* String result for command (malloc'ed).
142
+ * NULL means command still pending. */
143
+ char_u *encoding; /* Encoding of result (malloc'ed) */
144
+ } PendingCommand;
145
+
146
+ /*
147
+ * Forward declarations for procedures defined later in this file:
148
+ */
149
+ static void SendInit();
150
+ static void DeleteAnyLingerer __ARGS((Window w));
151
+ static void SendToVim __ARGS((char_u *name, char_u **cmd, int cmdMode,
152
+ char_u *cmd_enc, int timeout_sec,
153
+ char_u **result, int *code, char_u **server_enc));
154
+ static Window LookupName __ARGS((char_u *name, int delete, char_u **loose));
155
+ static int GetRegProp __ARGS((char_u **regPropp, long_u *numItemsp));
156
+ static int IsSerialName __ARGS((char_u *name));
157
+ static int WindowValid __ARGS((Window w));
158
+ static int AppendPropCarefully __ARGS((Window window, Atom property,
159
+ char_u *value, int length));
160
+ static void WaitForPend __ARGS((Window w, PendingCommand *pending,
161
+ int timeout_sec));
162
+ static void EventProc __ARGS((PendingCommand *pending));
163
+ static VALUE send2vim __ARGS((VALUE cmd, int cmdMode));
164
+ static VALUE send_keys __ARGS((VALUE self, VALUE keys));
165
+ static VALUE send_expr __ARGS((VALUE self, VALUE expr));
166
+ static VALUE send_keys2 __ARGS((int argc, VALUE *argv, VALUE self));
167
+
168
+ void Init_vim_client();
169
+ int x_error_handler __ARGS((Display *dpy, XErrorEvent *error_event));
170
+ int x_ioerror_handler __ARGS((Display *dpy));
171
+ void sigint_handler __ARGS((int sig));
172
+
173
+ static Display *commDisplay = NULL;
174
+ static Window commWindow = None;
175
+ static Atom commProperty = None;
176
+ static Atom registryProperty = None;
177
+ static Atom vimProperty = None;
178
+ static char_u *empty_prop = (char_u *)""; /* empty GetRegProp() result */
179
+ static volatile sig_atomic_t got_x_error = FALSE;
180
+
181
+ static const int asExpr = 1; /* send cmd as an Expression */
182
+ static const int asKeys = 2; /* send cmd as Keys */
183
+ static const int asKeys2 = 3; /* send cmd as Keys and wait for a
184
+ response (notification) */
185
+
186
+ static struct sigaction sigint_action = {
187
+ .sa_handler = sigint_handler,
188
+ .sa_flags = SA_RESTART
189
+ };
190
+ static struct sigaction prev_sigint_action;
191
+ static volatile sig_atomic_t got_sigint = 0;
192
+
193
+ VALUE m_VimClient; /* VimClient module */
194
+
195
+ /*
196
+ * Initialize the communication channels for sending commands and receiving
197
+ * results.
198
+ */
199
+ static void
200
+ SendInit() {
201
+ XErrorHandler old_handler;
202
+
203
+ /*
204
+ * Create the window used for communication, and set up an
205
+ * event handler for it.
206
+ */
207
+ old_handler = XSetErrorHandler(x_error_handler);
208
+ got_x_error = FALSE;
209
+
210
+ commDisplay = XOpenDisplay(NULL);
211
+ commProperty = XInternAtom(commDisplay, "Comm", FALSE);
212
+ vimProperty = XInternAtom(commDisplay, "Vim", FALSE);
213
+ registryProperty = XInternAtom(commDisplay, "VimRegistry", FALSE);
214
+
215
+ commWindow = XCreateSimpleWindow(
216
+ commDisplay, XDefaultRootWindow(commDisplay),
217
+ getpid(), 0, 10, 10, 0,
218
+ WhitePixel(commDisplay, DefaultScreen(commDisplay)),
219
+ WhitePixel(commDisplay, DefaultScreen(commDisplay)));
220
+ XSelectInput(commDisplay, commWindow, PropertyChangeMask);
221
+ /* WARNING: Do not step through this while debugging, it will hangup
222
+ * the X server! */
223
+ XGrabServer(commDisplay);
224
+ DeleteAnyLingerer(commWindow);
225
+ XUngrabServer(commDisplay);
226
+
227
+ /* Make window recognizable as a Vim window.
228
+ *
229
+ * This is required in order to receive "notification" responses
230
+ * to send_keys2(). These are sent using a server2client() call
231
+ * on the server. That function uses serverSendReply(), which
232
+ * verifies that the client window has a vimProperty.
233
+ * Note that this is not the same as registering as a Vim server.
234
+ * (see DeleteAnyLingerer)
235
+ */
236
+ XChangeProperty(commDisplay, commWindow, vimProperty, XA_STRING,
237
+ 8, PropModeReplace, (char_u *)VIM_VERSION_SHORT,
238
+ (int)STRLEN(VIM_VERSION_SHORT) + 1);
239
+
240
+ XSync(commDisplay, FALSE);
241
+ XSetErrorHandler(old_handler);
242
+
243
+ if (got_x_error)
244
+ rb_raise(e_Error, "Failed to initialize");
245
+ }
246
+
247
+ /* X Error handler, defined by XSetErrorHandler().
248
+ * Used to temporarily replace the default handler to check for errors.
249
+ */
250
+ int
251
+ x_error_handler(Display *dpy, XErrorEvent *error_event) {
252
+ got_x_error = TRUE;
253
+ return 0; // returned value is ignored
254
+ }
255
+
256
+ /* X IO Error handler, defined by XSetIOErrorHandler().
257
+ * Used during the WaitForPend loop. These are fatal IO errors.
258
+ */
259
+ int
260
+ x_ioerror_handler(Display *dpy) {
261
+ rb_raise(ex_XIOError, "Fatal X11 IO Error");
262
+ }
263
+
264
+ /* Used during WaitForPend to exit the loop if a SIGINT is received.
265
+ * We terminate the loop, free resources, restore the previous SIGINT
266
+ * handler, then re-raise the SIGINT. If allowed to proceed, any call
267
+ * waiting for a reply will return with Qnil.
268
+ */
269
+ void
270
+ sigint_handler(int sig) {
271
+ got_sigint = 1;
272
+ }
273
+
274
+ /*
275
+ * Delete any lingering occurrence of window id. We promise that any
276
+ * occurrence is not ours since it is not yet put into the registry (by us)
277
+ *
278
+ * This is necessary in the following scenario:
279
+ * 1. There is an old windowid for an exit'ed vim in the registry
280
+ * 2. We get that id for our commWindow but only want to send, not register.
281
+ * 3. The window will mistakenly be regarded valid because of own commWindow
282
+ */
283
+ static void
284
+ DeleteAnyLingerer(
285
+ Window win /* Window to remove */
286
+ ){
287
+ char_u *regProp, *entry = NULL;
288
+ char_u *p;
289
+ long_u numItems;
290
+ int_u wwin;
291
+
292
+ /*
293
+ * Read the registry property.
294
+ */
295
+ if (GetRegProp(&regProp, &numItems) == FAIL)
296
+ return;
297
+
298
+ /* Scan the property for the window id. */
299
+ for (p = regProp; (long_u)(p - regProp) < numItems; ) {
300
+ if (*p != 0) {
301
+ sscanf((char *)p, "%x", &wwin);
302
+
303
+ if ((Window)wwin == win) {
304
+ int lastHalf;
305
+
306
+ /* Copy down the remainder to delete entry */
307
+ entry = p;
308
+ while (*p != 0)
309
+ p++;
310
+
311
+ p++;
312
+ lastHalf = numItems - (p - regProp);
313
+
314
+ if (lastHalf > 0)
315
+ memmove(entry, p, lastHalf);
316
+
317
+ numItems = (entry - regProp) + lastHalf;
318
+ p = entry;
319
+ continue;
320
+ }
321
+ }
322
+
323
+ while (*p != 0)
324
+ p++;
325
+
326
+ p++;
327
+ }
328
+
329
+ if (entry != NULL) {
330
+ XChangeProperty(commDisplay, RootWindow(commDisplay, 0),
331
+ registryProperty, XA_STRING, 8,
332
+ PropModeReplace, regProp, (int)(p - regProp));
333
+ XSync(commDisplay, FALSE);
334
+ }
335
+
336
+ if (regProp != empty_prop)
337
+ XFree(regProp);
338
+ }
339
+
340
+ /*
341
+ * Send to an instance of Vim via the X display.
342
+ */
343
+ static void
344
+ SendToVim(
345
+ char_u *name, /* Where to send. */
346
+ char_u **cmd, /* What to send. */
347
+ int cmdMode, /* One of asExpr, asKeys, asKeys2 */
348
+ char_u *cmd_enc, /* 'encoding' of cmd string */
349
+ int timeout_sec, /* Timeout for responses */
350
+ char_u **result, /* Server reply for asExpr or asKeys2 */
351
+ int *code, /* Return code from eval'ed expression.
352
+ Applicable only for asExpr */
353
+ char_u **server_enc /* Encoding of response from server */
354
+ ){
355
+ Window w;
356
+ char_u *property;
357
+ int length;
358
+ int res;
359
+ char_u *loosename = NULL;
360
+ static int serial = 0; /* Running count of sent commands.
361
+ * Used to give each command a
362
+ * different serial number. */
363
+
364
+ /*
365
+ * Bind the server name to a communication window.
366
+ *
367
+ * Find any survivor with a serialno attached to the name if the
368
+ * original registrant of the wanted name is no longer present.
369
+ *
370
+ * Delete any lingering names from dead editors.
371
+ */
372
+ while (TRUE) {
373
+ w = LookupName(name, FALSE, &loosename);
374
+ /* Check that the window is hot */
375
+ if (w != None) {
376
+ if (!WindowValid(w)) {
377
+ LookupName(loosename ? loosename : name,
378
+ /*DELETE=*/TRUE, NULL);
379
+ continue;
380
+ }
381
+ }
382
+ break;
383
+ }
384
+
385
+ if (loosename != NULL) {
386
+ name = loosename;
387
+ vim_free(loosename);
388
+ }
389
+
390
+ if (w == None)
391
+ rb_raise(e_Error, "No registered Vim server found for '%s'", name);
392
+
393
+ /*
394
+ * Send the command to target interpreter by appending it to the
395
+ * comm window in the communication window.
396
+ * Length must be computed exactly!
397
+ *
398
+ * If the server has +multi_byte, the cmd string will be converted
399
+ * from cmd_enc to the encoding set on the server.
400
+ * The result (if asExpr or asKeys2) will be in the server's encoding.
401
+ * If the server does not have +multi_byte, cmd_enc will be ignored
402
+ * and the result will default to 'latin1' (ISO-8859-1).
403
+ */
404
+
405
+ length = STRLEN(name) + STRLEN(cmd_enc) + STRLEN(*cmd) + 14;
406
+ property = (char_u *)alloc((unsigned)length + 30);
407
+ sprintf((char *)property, "%c%c%c-n %s%c-E %s%c-s %s",
408
+ 0, (cmdMode == asExpr) ? 'c' : 'k', 0, name, 0, cmd_enc, 0, *cmd);
409
+
410
+ /* Add a back reference to our comm window */
411
+ serial++;
412
+ sprintf((char *)property + length, "%c-r %x %d",
413
+ 0, (int_u)commWindow, serial);
414
+ /* Add length of what "-r %x %d" resulted in, skipping the NUL. */
415
+ length += STRLEN(property + length + 1) + 1;
416
+
417
+ res = AppendPropCarefully(w, commProperty, property, length + 1);
418
+ vim_free(property);
419
+
420
+ if (res < 0)
421
+ rb_raise(e_Error, "Failed to send message to Vim server");
422
+
423
+ /* There is no answer for this - Keys are sent async */
424
+ if (cmdMode == asKeys)
425
+ return;
426
+
427
+ PendingCommand pending;
428
+ pending.mode = cmdMode;
429
+ pending.result = NULL;
430
+ pending.encoding = NULL;
431
+ /* code and serial are only applicable to asExpr */
432
+ pending.code = 0;
433
+ pending.serial = serial;
434
+
435
+ WaitForPend(w, &pending, timeout_sec);
436
+
437
+ *code = pending.code;
438
+ *result = pending.result;
439
+ *server_enc = pending.encoding;
440
+ }
441
+
442
+ /*
443
+ * Given a server name, see if the name exists in the registry for a
444
+ * particular display.
445
+ *
446
+ * If the given name is registered, return the ID of the window associated
447
+ * with the name. If the name isn't registered, then return 0.
448
+ *
449
+ * Side effects:
450
+ * If the registry property is improperly formed, then it is deleted.
451
+ * If "delete" is non-zero, then if the named server is found it is
452
+ * removed from the registry property.
453
+ */
454
+ static Window
455
+ LookupName(
456
+ char_u *name, /* Name of a server. */
457
+ int delete, /* If non-zero, delete info about name. */
458
+ char_u **loose /* Do another search matching -999 if not found
459
+ Return result here if a match is found */
460
+ ){
461
+ char_u *regProp, *entry;
462
+ char_u *p;
463
+ long_u numItems;
464
+ int_u returnValue;
465
+
466
+ /*
467
+ * Read the registry property.
468
+ */
469
+ if (GetRegProp(&regProp, &numItems) == FAIL)
470
+ return 0;
471
+
472
+ /*
473
+ * Scan the property for the desired name.
474
+ */
475
+ returnValue = (int_u)None;
476
+ entry = NULL; /* Not needed, but eliminates compiler warning. */
477
+ for (p = regProp; (long_u)(p - regProp) < numItems; ) {
478
+ entry = p;
479
+
480
+ while (*p != 0 && !isspace(*p))
481
+ p++;
482
+
483
+ // case insensitive, full string comparison
484
+ if (*p != 0 && STRICMP(name, p + 1) == 0) {
485
+ sscanf((char *)entry, "%x", &returnValue);
486
+ break;
487
+ }
488
+
489
+ while (*p != 0)
490
+ p++;
491
+
492
+ p++;
493
+ }
494
+
495
+ if (loose != NULL && returnValue == (int_u)None && !IsSerialName(name)) {
496
+ for (p = regProp; (long_u)(p - regProp) < numItems; ) {
497
+ entry = p;
498
+
499
+ while (*p != 0 && !isspace(*p))
500
+ p++;
501
+
502
+ // case insensitive, only strlen(name) of (p + 1) compared
503
+ if (*p != 0 && IsSerialName(p + 1)
504
+ && STRNICMP(name, p + 1, STRLEN(name)) == 0) {
505
+ sscanf((char *)entry, "%x", &returnValue);
506
+ *loose = vim_strsave(p + 1);
507
+ break;
508
+ }
509
+
510
+ while (*p != 0)
511
+ p++;
512
+
513
+ p++;
514
+ }
515
+ }
516
+
517
+ /*
518
+ * Delete the property, if that is desired (copy down the
519
+ * remainder of the registry property to overlay the deleted
520
+ * info, then rewrite the property).
521
+ */
522
+ if (delete && returnValue != (int_u)None) {
523
+ int count;
524
+
525
+ while (*p != 0)
526
+ p++;
527
+
528
+ p++;
529
+ count = numItems - (p - regProp);
530
+ if (count > 0)
531
+ memmove(entry, p, count);
532
+
533
+ XChangeProperty(commDisplay, RootWindow(commDisplay, 0),
534
+ registryProperty, XA_STRING,
535
+ 8, PropModeReplace, regProp,
536
+ (int)(numItems - (p - entry)));
537
+ XSync(commDisplay, FALSE);
538
+ }
539
+
540
+ if (regProp != empty_prop)
541
+ XFree(regProp);
542
+
543
+ return (Window)returnValue;
544
+ }
545
+
546
+ /*
547
+ * Read the registry property. Delete it when it's formatted wrong.
548
+ * Return the property in "regPropp". "empty_prop" is used when it doesn't
549
+ * exist yet.
550
+ * Return OK when successful.
551
+ */
552
+ static int
553
+ GetRegProp(
554
+ char_u **regPropp,
555
+ long_u *numItemsp
556
+ ){
557
+ int result, actualFormat;
558
+ long_u bytesAfter;
559
+ Atom actualType;
560
+ XErrorHandler old_handler;
561
+
562
+ *regPropp = NULL;
563
+ old_handler = XSetErrorHandler(x_error_handler);
564
+ got_x_error = FALSE;
565
+
566
+ result = XGetWindowProperty(commDisplay, RootWindow(commDisplay, 0),
567
+ registryProperty, 0L, (long)MAX_PROP_WORDS,
568
+ FALSE, XA_STRING, &actualType, &actualFormat,
569
+ numItemsp, &bytesAfter, regPropp);
570
+
571
+ XSync(commDisplay, FALSE);
572
+ XSetErrorHandler(old_handler);
573
+
574
+ if (got_x_error)
575
+ return FAIL;
576
+
577
+ if (actualType == None) {
578
+ /* No prop yet. Logically equal to the empty list */
579
+ *numItemsp = 0;
580
+ *regPropp = empty_prop;
581
+ return OK;
582
+ }
583
+
584
+ /* If the property is improperly formed, then delete it. */
585
+ if (result != Success || actualFormat != 8 || actualType != XA_STRING) {
586
+ if (*regPropp != NULL)
587
+ XFree(*regPropp);
588
+ XDeleteProperty(commDisplay, RootWindow(commDisplay, 0),
589
+ registryProperty);
590
+
591
+ return FAIL;
592
+ }
593
+
594
+ return OK;
595
+ }
596
+
597
+ /*
598
+ * Check if "str" looks like it had a serial number appended.
599
+ * Actually just checks if the name ends in a digit.
600
+ *
601
+ * Handles characters > 0x100. We don't use isdigit() here, because on some
602
+ * systems it also considers superscript 1 to be a digit.
603
+ */
604
+ static int
605
+ IsSerialName(char_u *str) {
606
+ int len = STRLEN(str);
607
+
608
+ if (len > 1) {
609
+ int c = str[len - 1];
610
+ return (c >= '0' && c <= '9');
611
+ }
612
+
613
+ return 0;
614
+ }
615
+
616
+ /*
617
+ * Return TRUE if window "w" exists and has a "Vim" property on it.
618
+ */
619
+ static int
620
+ WindowValid(Window w) {
621
+ XErrorHandler old_handler;
622
+ Atom *plist;
623
+ int numProp;
624
+ int i;
625
+
626
+ old_handler = XSetErrorHandler(x_error_handler);
627
+ got_x_error = 0;
628
+ plist = XListProperties(commDisplay, w, &numProp);
629
+ XSync(commDisplay, FALSE);
630
+ XSetErrorHandler(old_handler);
631
+
632
+ if (plist == NULL || got_x_error)
633
+ return FALSE;
634
+
635
+ for (i = 0; i < numProp; i++) {
636
+ if (plist[i] == vimProperty) {
637
+ XFree(plist);
638
+ return TRUE;
639
+ }
640
+ }
641
+
642
+ XFree(plist);
643
+ return FALSE;
644
+ }
645
+
646
+ /*
647
+ * Append a given property to a given window, but set up an X error handler so
648
+ * that if the append fails this procedure can return an error code rather
649
+ * than having Xlib panic.
650
+ * Return: 0 for OK, -1 for error
651
+ */
652
+ static int
653
+ AppendPropCarefully(
654
+ Window window, /* Window whose property is to be modified. */
655
+ Atom property, /* Name of property. */
656
+ char_u *value, /* Characters to append to property. */
657
+ int length /* How much to append */
658
+ ){
659
+ XErrorHandler old_handler;
660
+
661
+ old_handler = XSetErrorHandler(x_error_handler);
662
+ got_x_error = FALSE;
663
+ XChangeProperty(commDisplay, window, property, XA_STRING, 8,
664
+ PropModeAppend, value, length);
665
+ XSync(commDisplay, FALSE);
666
+ XSetErrorHandler(old_handler);
667
+
668
+ return got_x_error ? -1 : 0;
669
+ }
670
+
671
+ /*
672
+ * Enter a loop processing X events & polling chars until we see a result
673
+ */
674
+ static void
675
+ WaitForPend(
676
+ Window w,
677
+ PendingCommand *pending,
678
+ int timeout_sec
679
+ ){
680
+ time_t start;
681
+ time_t now;
682
+ time_t lastChk = 0;
683
+ int timed_out = 0;
684
+ XEvent event;
685
+ XPropertyEvent *e = (XPropertyEvent *)&event;
686
+
687
+ #define MSEC_POLL 500
688
+ #ifdef HAVE_SYS_SELECT_H
689
+ fd_set fds;
690
+ struct timeval tv;
691
+
692
+ tv.tv_sec = 0;
693
+ tv.tv_usec = MSEC_POLL * 1000;
694
+ FD_ZERO(&fds);
695
+ FD_SET(ConnectionNumber(commDisplay), &fds);
696
+ #else
697
+ struct pollfd fds;
698
+
699
+ fds.fd = ConnectionNumber(commDisplay);
700
+ fds.events = POLLIN;
701
+ #endif
702
+
703
+ got_sigint = 0;
704
+ sigaction(SIGINT, &sigint_action, &prev_sigint_action);
705
+
706
+ /* We'll raise VimClient::X11IOError if any fatal IO errors
707
+ * are raised by X11 while waiting for a response
708
+ */
709
+ XIOErrorHandler old_handler = XSetIOErrorHandler(x_ioerror_handler);
710
+
711
+ /* Watch for updates to our commProperty until we timeout or we're
712
+ * interrupted. We'll check the queue upon entering the loop, then
713
+ * use select/poll to check the queue as soon as our connection's fd
714
+ * has data (which X11/xcb will read), or at every MSEC_POLL interval.
715
+ */
716
+ time(&start);
717
+ while ((pending->result == NULL) && !got_sigint)
718
+ {
719
+ while (XEventsQueued(commDisplay, QueuedAfterReading) > 0) {
720
+ XNextEvent(commDisplay, &event);
721
+ if (event.type == PropertyNotify && e->window == commWindow &&
722
+ e->atom == commProperty && e->state == PropertyNewValue)
723
+ EventProc(pending);
724
+ }
725
+
726
+ if (pending->result == NULL) {
727
+
728
+ time(&now);
729
+ if ((now - start) >= timeout_sec) {
730
+ timed_out = 1;
731
+ break;
732
+ }
733
+
734
+ #ifdef HAVE_SYS_SELECT_H
735
+ select(FD_SETSIZE, &fds, NULL, NULL, &tv);
736
+ #else
737
+ poll(&fds, 1, MSEC_POLL);
738
+ #endif
739
+ }
740
+ }
741
+
742
+ XSetIOErrorHandler(old_handler);
743
+
744
+ if (got_sigint) {
745
+ /* When the previous handler is restored and we re-raise SIGINT,
746
+ * even if the signal is ignored, we will return Qnil.
747
+ * Also, make sure any malloc'ed memory is cleared,
748
+ * since it's possible EventProc could have stored a result already.
749
+ */
750
+ vim_free(pending->result);
751
+ vim_free(pending->encoding);
752
+ pending->result = NULL;
753
+ }
754
+
755
+ sigaction(SIGINT, &prev_sigint_action, NULL);
756
+
757
+ if (got_sigint)
758
+ kill(0, SIGINT);
759
+
760
+ if (timed_out)
761
+ rb_raise(e_TimeoutError, "Timed out waiting for response");
762
+ }
763
+
764
+ /*
765
+ * This procedure is invoked when our commProperty is updated.
766
+ * It reads the response from the server and stores the results
767
+ * in the PendingCommand structure.
768
+ */
769
+ static void
770
+ EventProc(PendingCommand *pending) {
771
+ char_u *propInfo;
772
+ char_u *p;
773
+ int result, actualFormat, code;
774
+ long_u numItems, bytesAfter;
775
+ Atom actualType;
776
+
777
+ /*
778
+ * Read the comm property and delete it.
779
+ * Note: There's no guarantee this won't result in a partial read.
780
+ */
781
+ propInfo = NULL;
782
+ result = XGetWindowProperty(commDisplay, commWindow, commProperty,
783
+ 0L, (long)MAX_PROP_WORDS, TRUE,
784
+ XA_STRING, &actualType, &actualFormat,
785
+ &numItems, &bytesAfter, &propInfo);
786
+
787
+ /* If the property doesn't exist or is improperly formed then ignore it. */
788
+ if (result != Success || actualType != XA_STRING || actualFormat != 8) {
789
+ if (propInfo != NULL)
790
+ XFree(propInfo);
791
+ return;
792
+ }
793
+
794
+ /*
795
+ * It is possible for multiple results to be stored in the commProperty.
796
+ * Each iteration through the outer loop handles a single result.
797
+ */
798
+ for (p = propInfo; (long_u)(p - propInfo) < numItems; ) {
799
+ /*
800
+ * Ignore leading NULs; each result starts with a NUL
801
+ * so that no matter how badly formed a preceding result is,
802
+ * we'll be able to tell that a new result is starting.
803
+ */
804
+ if (*p == 0) {
805
+ p++;
806
+ continue;
807
+ }
808
+
809
+ if ((*p == 'r' || *p == 'n') && p[1] == 0) {
810
+ int resMode, serial, gotSerial;
811
+ char_u *res, *enc;
812
+
813
+ /*
814
+ * This is a reply to send_expr() or send_keys2().
815
+ * The response to send_expr() arrives as a reply ('r').
816
+ * The response to send_keys2() arrives as a notification ('n').
817
+ * Since we wait for a response to each of these requests, if a
818
+ * response is present for the other type, it will be discarded.
819
+ */
820
+ resMode = (*p == 'r') ? asExpr : asKeys2;
821
+ if (pending->mode != resMode) {
822
+ p++;
823
+ continue;
824
+ }
825
+
826
+ /*
827
+ * Iterate over all of its options. Stop when we reach the end
828
+ * of the property or something that doesn't look like an option.
829
+ */
830
+ p += 2;
831
+ gotSerial = 0;
832
+ code = 0;
833
+ res = NULL;
834
+ enc = NULL;
835
+ while ((long_u)(p - propInfo) < numItems && *p == '-') {
836
+ switch (p[1]) {
837
+ case 'r': /* Response to send_expr() */
838
+ if (p[2] == ' ')
839
+ res = p + 3;
840
+ break;
841
+ case 'n': /* Response to send_keys2() */
842
+ if (p[2] == ' ')
843
+ res = p + 3;
844
+ break;
845
+ case 'E': /* Encoding of 'r' or 'n' */
846
+ if (p[2] == ' ')
847
+ enc = p + 3;
848
+ break;
849
+ case 's': /* send_expr() only */
850
+ if (sscanf((char *)p + 2, " %d", &serial) == 1)
851
+ gotSerial = 1;
852
+ break;
853
+ case 'c': /* send_expr() only */
854
+ if (sscanf((char *)p + 2, " %d", &code) != 1)
855
+ code = 0;
856
+ break;
857
+ /* case 'w': (server's commWindow)
858
+ * send_keys2() only - ignored. */
859
+ }
860
+
861
+ while (*p != 0)
862
+ p++;
863
+
864
+ p++;
865
+ }
866
+
867
+ /* If we're looking for a response to send_expr()
868
+ * but it does not match the serial number in the pending
869
+ * request, discard it. It could be a reply from a previous
870
+ * request which either timed-out or was aborted via SIGINT.
871
+ */
872
+ if (!gotSerial && resMode == asExpr)
873
+ continue;
874
+
875
+ /* Responses to send_keys2() are not serialized.
876
+ * This means if there are old responses in the commProperty,
877
+ * each one found will be stored in pending, leaving us with
878
+ * only the latest response. So we must call vim_free() to
879
+ * clear any previous results.
880
+ *
881
+ * Note: Since notifications ('n') are not serialized, if an old
882
+ * PropertyChange and response were present (due to a previous
883
+ * timeout or interrupt) when we entered the loop in WaitForPend,
884
+ * it's possible we could read that here before the recent reply
885
+ * we want arrives. And since pending->result will not be NULL,
886
+ * the WaitForPend loop will exit.
887
+ */
888
+ if (resMode == asKeys2 ||
889
+ (resMode == asExpr && serial == pending->serial)) {
890
+ vim_free(pending->result);
891
+ vim_free(pending->encoding);
892
+ pending->code = code; /* always 0 for asKeys2 */
893
+ pending->result = (res != NULL) ?
894
+ vim_strsave(res) : vim_strsave((char_u *)"");
895
+ /* If server does not have +multi_byte,
896
+ * use Vim's default 'latin1' encoding */
897
+ pending->encoding = (enc != NULL) ?
898
+ vim_strsave(enc) : vim_strsave((char_u *)"latin1");
899
+ }
900
+ } else {
901
+ /*
902
+ * Didn't recognize this thing. Just skip through the next
903
+ * null character and try again.
904
+ */
905
+ while (*p != 0)
906
+ p++;
907
+
908
+ p++;
909
+ }
910
+ }
911
+ XFree(propInfo);
912
+ }
913
+
914
+ /*
915
+ * Wrapper for SendToVim()
916
+ */
917
+ static VALUE
918
+ send2vim(VALUE cmd_str, int cmdMode) {
919
+ char_u *name = NULL; // name of server to send to
920
+ char_u *cmd = NULL; // keys or expr to send
921
+ char_u *cmd_enc = NULL; // encoding of cmd sent to server
922
+ char_u *res = NULL; // response from the server
923
+ char_u *server_enc = NULL; // encoding of response from server
924
+ int code = 0; // response code (0 = OK, 1 = FAIL)
925
+ int timeout_sec = 60; // timeout for waiting for a response
926
+
927
+ VALUE server = rb_iv_get(m_VimClient, "@server_name");
928
+ if (NIL_P(server))
929
+ rb_raise(e_Error, "server_name must be set");
930
+ else
931
+ name = StringValueCStr(server);
932
+
933
+ if (cmdMode != asKeys) {
934
+ /* override the default timeout if VimClient.timeout_sec is set */
935
+ VALUE val = rb_iv_get(m_VimClient, "@timeout_sec");
936
+ int t = FIXNUM_P(val) ? FIX2INT(val) : 0;
937
+ if (t > 0)
938
+ timeout_sec = t;
939
+ }
940
+
941
+ /* Convert the ruby string into a C string, transcoding as needed.
942
+ * Set the Vim encoding name of the cmd string. */
943
+ cmd = (char_u *)rb2vim_enc_str(cmd_str, &cmd_enc);
944
+
945
+ SendToVim(name, &cmd, cmdMode, cmd_enc, timeout_sec,
946
+ &res, &code, &server_enc);
947
+
948
+ if (cmdMode != asKeys && res != NULL) {
949
+ /* Convert the returned C string into a Ruby string,
950
+ * marked with the proper encoding */
951
+ VALUE ret = vim2rb_enc_str(&res, server_enc);
952
+ /* malloc'ed by vim_strsave() in EventProc() */
953
+ vim_free(res);
954
+ vim_free(server_enc);
955
+
956
+ if (code)
957
+ rb_raise(e_ExprError, "%s", StringValueCStr(ret));
958
+ else
959
+ return ret;
960
+ }
961
+
962
+ return Qnil;
963
+ }
964
+
965
+ static VALUE
966
+ send_keys(VALUE self, VALUE keys) {
967
+ return send2vim(keys, asKeys);
968
+ }
969
+
970
+ static VALUE
971
+ send_expr(VALUE self, VALUE expr) {
972
+ return send2vim(expr, asExpr);
973
+ }
974
+
975
+ static VALUE
976
+ send_keys2(int argc, VALUE *argv, VALUE self) {
977
+ VALUE keys, expr;
978
+ rb_scan_args(argc, argv, "11", &keys, &expr);
979
+
980
+ /* Create a callback string using the same encoding as keys.
981
+ * Note that concatination must be used, as opposed to printf (%)
982
+ * since we could be dealing with 16/32 bit encodings at this point.
983
+ */
984
+ VALUE callback, close;
985
+ rb_encoding *enc = rb_enc_get(keys);
986
+ callback = rb_str_export_to_enc(rb_str_new2(
987
+ ":call server2client(expand(\"<client>\"), "), enc);
988
+ close = rb_str_export_to_enc(rb_str_new2(")<cr>"), enc);
989
+
990
+ if (NIL_P(expr)) /* Return an empty string from the callback */
991
+ expr = rb_str_export_to_enc(rb_str_new2("\"\""), enc);
992
+ else if (enc != rb_enc_get(expr))
993
+ rb_raise(e_Error, "'keys' and 'expr' must use the same encoding");
994
+
995
+ /* Insert expr into the callback */
996
+ rb_str_append(callback, expr);
997
+ rb_str_append(callback, close);
998
+
999
+ /* Append the callback to the keys.
1000
+ * rb_str_plus is used so we don't modify the user's 'keys' string */
1001
+ keys = rb_str_plus(keys, callback);
1002
+
1003
+ return send2vim(keys, asKeys2);
1004
+ }
1005
+
1006
+ /*
1007
+ * Initialization method for ruby extension
1008
+ */
1009
+ void
1010
+ Init_vim_client() {
1011
+ m_VimClient = rb_define_module("VimClient");
1012
+
1013
+ VALUE ex_Exception =
1014
+ rb_define_class_under(m_VimClient, "Exception", rb_eException);
1015
+ ex_NoMemoryError =
1016
+ rb_define_class_under(m_VimClient, "NoMemoryError", ex_Exception);
1017
+ ex_XIOError =
1018
+ rb_define_class_under(m_VimClient, "XIOError", ex_Exception);
1019
+ e_Error =
1020
+ rb_define_class_under(m_VimClient, "Error", rb_eStandardError);
1021
+ e_TimeoutError =
1022
+ rb_define_class_under(m_VimClient, "TimeoutError", e_Error);
1023
+ e_ExprError =
1024
+ rb_define_class_under(m_VimClient, "ExprError", e_Error);
1025
+
1026
+ rb_define_singleton_method(m_VimClient, "send_keys", send_keys, 1);
1027
+ rb_define_singleton_method(m_VimClient, "send_expr", send_expr, 1);
1028
+ rb_define_singleton_method(m_VimClient, "send_keys2", send_keys2, -1);
1029
+
1030
+ VALUE klass = rb_funcall(m_VimClient, rb_intern("singleton_class"), 0);
1031
+ rb_define_attr(klass, "server_name", Qtrue, Qtrue);
1032
+ rb_define_attr(klass, "timeout_sec", Qtrue, Qtrue);
1033
+ /* initialize instance variables (eliminates warnings) */
1034
+ rb_iv_set(m_VimClient, "@server_name", Qnil);
1035
+ rb_iv_set(m_VimClient, "@timeout_sec", Qnil);
1036
+
1037
+ /* Initialize X connection */
1038
+ SendInit();
1039
+ }