~ubuntu-branches/ubuntu/saucy/pida/saucy

« back to all changes in this revision

Viewing changes to src/plugins/vim/gdkvim.py

  • Committer: Bazaar Package Importer
  • Author(s): Barry deFreese
  • Date: 2006-08-01 13:08:56 UTC
  • mfrom: (0.1.2 etch) (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20060801130856-v92ktopgdxc8rv7q
Tags: 0.3.1-2ubuntu1
* Re-sync with Debian
* Remove bashisms from debian/rules

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*- 
2
 
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
3
 
# $Id: gdkvim.py 453 2005-07-24 15:24:10Z aafshar $
4
 
#Copyright (c) 2005 Ali Afshar aafshar@gmail.com
5
 
 
6
 
#Permission is hereby granted, free of charge, to any person obtaining a copy
7
 
#of this software and associated documentation files (the "Software"), to deal
8
 
#in the Software without restriction, including without limitation the rights
9
 
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
 
#copies of the Software, and to permit persons to whom the Software is
11
 
#furnished to do so, subject to the following conditions:
12
 
 
13
 
#The above copyright notice and this permission notice shall be included in
14
 
#all copies or substantial portions of the Software.
15
 
 
16
 
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
 
#SOFTWARE.
23
 
 
24
 
 
25
 
"""
26
 
A library to control vim -g using its X protocol interface (with gdk).
27
 
 
28
 
== How it works ==
29
 
 
30
 
=== General Communication ===
31
 
 
32
 
The Vim client/server protocol communicates by sending messages to and from an
33
 
X communication window. The details are explained in the Vim source.
34
 
Essentially, Vim understands two sorts of messages over this interface.
35
 
 
36
 
;asynchronous key sends : that are exactly equivalent to to the user of the
37
 
remote Vim typing commands.
38
 
 
39
 
;synchronous expression evaluations : these are Vim expressions that are
40
 
evaluated by the remote Vim, and an answer is replied with the result over the
41
 
same protocol.
42
 
 
43
 
Although the synchronous messages are called synchronous, the reply itself, in
44
 
programming terms is entirely asynchronous, in that there is no way of knowing
45
 
when a reply will be received, and one cannot block for it.
46
 
 
47
 
Thus, this library allows you to make both of these calls to remote Vims.
48
 
Synchronous expressions must provide a call back function that will be called
49
 
when the message is replied to.
50
 
 
51
 
=== The Server List ===
52
 
 
53
 
(It has been an utter nightmare.)
54
 
 
55
 
The primary problem is that GTK does not actually know accurately whether
56
 
a window with a given window ID has been destroyed. This is how Vim does
57
 
it (using the X libraries) after checking an attribute for registered Vim
58
 
sessions with the X root window. This way each Vim doesn't need to
59
 
unregister itself with the X root window on dying, it just assumes that
60
 
any other client attempting to connect to it will know that the window
61
 
has been destroyed. As mentioned, GTK totally fails to do what the X
62
 
library does, and ascertain whether the window is alive. It succeeds
63
 
sometimes, but not at others. The result is a GDK window that appears
64
 
alive, and ready to communicate with, but which causes an uncatchable and
65
 
fatal application error.
66
 
 
67
 
Step in other potential methods of getting an accurate list of servers.
68
 
Firstly, and most obviously, one can call the command 'vim --serverlist'
69
 
on a simple system pipe and read the list off. This is entirely reliable,
70
 
and effective, but the cost of forking a process and starting Vim each
71
 
time is not fun, and effectively blocks.
72
 
 
73
 
Another option is to force users to start Vim through Pida and keep an
74
 
account of the child processes. This would work very effectively, but it
75
 
restricts the user, and the entire system.
76
 
 
77
 
The final, and current solution is to start Vim itself on a
78
 
pseudoterminal as a hidden instance, and then communicate with that over
79
 
the Vim protocol. The reason this can be reliably done, is that since the
80
 
process is a child, it can be polled to check whether it is alive. This
81
 
is performed each time the serverlist is requested, and if the hidden
82
 
instance has been destroyed (eg by the user) a new one is spawned, thus
83
 
preventing an attempt to communicate with an already-destroyed GDK
84
 
window.
85
 
 
86
 
The cost of this solution is that we spawn an extra Vim process. I
87
 
believe that the added solidity it brings to the entire system is easily
88
 
worth it, and it ensures that Pida can communicate with Vim it started
89
 
and Vim it didn't start.
90
 
"""
91
 
# Gtk imports
92
 
import gtk
93
 
import gobject
94
 
import gtk.gdk as gdk
95
 
# System imports
96
 
import os
97
 
import pty
98
 
import sys
99
 
import time
100
 
import pida.base as base
101
 
 
102
 
class VimHidden(base.pidaobject):
103
 
    """
104
 
    An instance of Vim on a pseudoterminal which can be reliably polled.
105
 
 
106
 
    This class is used to provide an instance of Vim which can be communicated
107
 
    with using the Vim client/server protocol, in order to retrieve an accurate
108
 
    and current server list, and also which can be polled accurately as to
109
 
    whether it is alive before communicating with it.
110
 
 
111
 
    This method is much cheaper in resources than running vim --serverlist each
112
 
    time, and much more accurate than using the root window's VimRegistry
113
 
    property, and also more accurate than using GDK methods for assessing
114
 
    whether a window is alive.
115
 
    """
116
 
 
117
 
    def do_init(self):
118
 
        """
119
 
        Constructor.
120
 
        
121
 
        Create a temporary and unique name for use as the servername, and
122
 
        initialise the instance variables.
123
 
 
124
 
        @param cb: An instance of the main application class.
125
 
        @type cb: pida.main.Application.
126
 
        """
127
 
        # Prefacing with '__' means it will be ignored in the internal server
128
 
        # list.
129
 
        self.name = '__%s_PIDA_HIDDEN' % time.time()
130
 
        # Checked to evaluate False on starting.
131
 
        self.pid = None
132
 
 
133
 
    def start(self):
134
 
        """
135
 
        Start the Vim instance if it is not already running.
136
 
        
137
 
        This command forks in a pseudoterminal, and starts Vim, if Vim is not
138
 
        already running. The pid is stored for later use.
139
 
        """
140
 
        if not self.pid:
141
 
            # Get the console vim executable path
142
 
            command = self.prop_main_registry.commands.vim.value()
143
 
            # Fork using pty.fork to prevent Vim taking the terminal
144
 
            sock = gtk.Socket()
145
 
            w = gtk.Window()
146
 
            w.realize()
147
 
            w.add(sock)
148
 
            xid = sock.get_id()
149
 
            pid, fd = pty.fork()
150
 
            if pid == 0:
151
 
                # Child, execute Vim with the correct servername argument
152
 
                os.execvp(command, ['gvim', '-f', '--servername', self.name,
153
 
                    '--socketid', '%s' % xid])
154
 
                    #'-v'])
155
 
                # os.system('%s -v --servername %s' % (command, self.name))
156
 
            else:
157
 
                # Parent, store the pid, and file descriptor for later.
158
 
                self.pid = pid
159
 
                self.childfd = fd
160
 
                self.do_action('accountfork', self.pid)
161
 
 
162
 
    def is_alive(self):
163
 
        """
164
 
        Check if the Vim instance is alive.
165
 
        
166
 
        This method uses os.waitpid, with no blocking to determine whether the
167
 
        process is still alive. If it is not, it sets the internal pid
168
 
        attribute to None, so that it may be restarted.
169
 
        
170
 
        @returns: alive
171
 
        @rtype alive: boolean
172
 
        """
173
 
        if self.pid:
174
 
            try:
175
 
                # call os.waitpid, returns 0 if the pid is alive
176
 
                pid, sts = os.waitpid(self.pid, os.WNOHANG)
177
 
            except OSError:
178
 
                # might still be starting up
179
 
                return False
180
 
            if pid == self.pid:
181
 
                # has shut down
182
 
                self.pid = None
183
 
                return False
184
 
            else:
185
 
                # is still alive
186
 
                return True
187
 
        else:
188
 
            # Not started yet
189
 
            return False
190
 
 
191
 
class VimWindow(base.pidaobject, gtk.Window):
192
 
    """
193
 
    A GTK window that can communicate with any number Vim instances.
194
 
 
195
 
    This is an actual GTK window (which it must be to accurately detect
196
 
    property events inside the GTK main loop) but has its GDK window correctly
197
 
    set to receive such events. This is notably the "Vim" property which must
198
 
    be present and set to a version string, in this case "6.0" is used.
199
 
    """
200
 
    def do_init(self):
201
 
        """
202
 
        Constructor.
203
 
 
204
 
        The Window is instantiated, the properties are correctly set, the event
205
 
        mask is modified, and the instance variables are initialized.
206
 
 
207
 
        @param cb: An instance of the main Application class.
208
 
        @type cb: pida.main.Application.
209
 
        """
210
 
        gtk.Window.__init__(self)
211
 
        # Window needs to be realized to do anything useful with it. Realizing
212
 
        # does not show the window to the user, so we can use it, but not have
213
 
        # an ugly blank frame while it loads.
214
 
        self.realize()
215
 
        # The "Vim" property
216
 
        self.window.property_change("Vim", gdk.SELECTION_TYPE_STRING, 8,
217
 
                            gdk.PROP_MODE_REPLACE, "6.0")
218
 
        # Set the correct event mask and connect the notify event
219
 
        self.add_events(gtk.gdk.PROPERTY_CHANGE_MASK)
220
 
        self.connect('property-notify-event', self.cb_notify)
221
 
        # The serial number used for sending synchronous messages
222
 
        self.serial = 1
223
 
        # A dictionary of callbacks for synchronous messages. The key is the
224
 
        # serial number, and the value is a callable that will be called with
225
 
        # the result of the synchronous evaluation.
226
 
        self.callbacks = {}
227
 
        # A dictionary to store the working directories for each Vim so they
228
 
        # only have to be fetched once.
229
 
        self.server_cwds = {}
230
 
        # A list of the last fetched server list, used todecide whther to feed
231
 
        # the most recent server list to the client.
232
 
        self.oldservers = None
233
 
        # An instance of the root window, so it only has to be fetched once.
234
 
        self.root_window = gdk.get_default_root_window()
235
 
        # Instantiate and start the hidden communication window, used for
236
 
        # fetching accurate and reliable server lists. 
237
 
        self.vim_hidden = VimHidden()
238
 
        self.vim_hidden.start()
239
 
        # Start the timer for fetching the server list at the specified number
240
 
        # of milliseconds (500).
241
 
        gobject.timeout_add(1000, self.fetch_serverlist)
242
 
    
243
 
    def fetch_serverlist(self):
244
 
        """
245
 
        Fetch the serverlist, and if it has changed, feed it to the client.
246
 
 
247
 
        The serverlist is requested asynchrnously, and passed the gotservers
248
 
        function as a callback. The gotservers function is then called with the
249
 
        server list, gets the appropriate working directory (if required) and
250
 
        feeds the new server list to the client if it has changed.
251
 
        """
252
 
        def gotservers(serverlist):
253
 
            """
254
 
            Called back on receiving the serverlist.
255
 
 
256
 
            Fetch working directories for new Vim instances, and feed the
257
 
            server list to the client if it has changed.
258
 
            """
259
 
            for server in serverlist:
260
 
                # Check if we already have the working directory.
261
 
                if server not in self.server_cwds:
262
 
                    # We don't, fetch it
263
 
                    self.fetch_cwd(server)
264
 
            # Check if the server list has changed
265
 
            if serverlist != self.oldservers:
266
 
                self.oldservers = serverlist
267
 
                # A ew serverlist to feed to the client.
268
 
                self.feed_serverlist(serverlist)
269
 
        # Fetch the server list from the hidden vim instance with the
270
 
        # gotservers function as a callback.
271
 
        self.get_hidden_serverlist(gotservers)
272
 
        # Return True so that the timer continues
273
 
        return True
274
 
 
275
 
    def get_rootwindow_serverlist(self):
276
 
        """
277
 
        Get the X root window's version of the current Vim serverlist.
278
 
 
279
 
        On starting with the client-server feature, GVim or Vim with the
280
 
        --servername option registers its server name and X window id as part
281
 
        of the "VimRegistry" parameter on the X root window.
282
 
 
283
 
        This method extracts and parses that property, and returns the server
284
 
        list.
285
 
 
286
 
        Note: Vim does not actually unregister itself with the root window on
287
 
        dying, so the presence of a server in the root window list is no
288
 
        gurantee that it is alive.
289
 
 
290
 
        @return: servers
291
 
        @rtype servers: dict of ("server", "window id") key, value
292
 
        """
293
 
        servers = {}
294
 
        # Read the property
295
 
        vimregistry = self.root_window.property_get("VimRegistry")
296
 
        # If it exists
297
 
        if vimregistry:
298
 
            # Get the list of servers by splitting with '\0'
299
 
            vimservers = vimregistry[-1].split('\0')
300
 
            # Parse each individual server and add to the results list
301
 
            for rawserver in vimservers:
302
 
                # Sometimes blank servers exist in the list
303
 
                if rawserver:
304
 
                    # split the raw value for the name and id
305
 
                    name_id = rawserver.split()
306
 
                    # Set the value in the results dict, remembering to convert
307
 
                    # the window id to a long int.
308
 
                    servers[name_id[1]] = long(int(name_id[0], 16))
309
 
        # return the list of resuts
310
 
        return servers
311
 
 
312
 
    def get_shell_serverlist(self):
313
 
        """
314
 
        Get the server list by starting console Vim on a Pipe.
315
 
 
316
 
        This blocks, so we don't use it. It is one of the alternative methods
317
 
        of retrieving an accurate serverlist. It is slow, and expensive.
318
 
        """
319
 
        vimcom = prop_main_registry.commands.vim_console.value()
320
 
        p = os.popen('%s --serverlist' % vimcom)
321
 
        servers = p.read()
322
 
        p.close()
323
 
        return servers.splitlines()
324
 
 
325
 
    def get_hidden_serverlist(self, callbackfunc):
326
 
        """
327
 
        Get the serverlist from the hidden Vim instance and call the callback
328
 
        function with the results.
329
 
 
330
 
        This method checks first whther the Vim instance is alive, and then
331
 
        evaluates the serverlist() function remotely in it, with a local call
332
 
        back function which parses the result and calls the user-provided
333
 
        callback function.
334
 
 
335
 
        @param callbackfunc: The call back function to be called with the
336
 
            server list.
337
 
        @type callbackfunc: callable
338
 
        """
339
 
        def cb(serverstring):
340
 
            """
341
 
            Called back with the raw server list.
342
 
 
343
 
            Parse the lines and call the call back function, ignoring any
344
 
            instances starting with "__" which represent hidden instances. If
345
 
            the hidden Vim instance is not alive, it is restarted.
346
 
            """
347
 
            servers = serverstring.splitlines()
348
 
            # Call the callback function
349
 
            callbackfunc([svr for svr in servers if not svr.startswith('__')])
350
 
        # Check if the hidden Vim is alive. 
351
 
        if self.vim_hidden.is_alive():
352
 
            # It is alive, get the serverlist.
353
 
            self.send_expr(self.vim_hidden.name, 'serverlist()', cb)
354
 
        else:
355
 
            # It is not alive, restart it.
356
 
            self.vim_hidden.start()
357
 
        
358
 
    def get_server_wid(self, servername):
359
 
        """
360
 
        Get the X Window id for a named Vim server.
361
 
 
362
 
        This function returns the id from the root window server list, if it
363
 
        exists, or None if it does not.
364
 
 
365
 
        @param servername: The name of the server
366
 
        @type servername: str
367
 
 
368
 
        @return: wid
369
 
        @rtype wid: long
370
 
        """
371
 
        try:
372
 
            # get the window id from the root window
373
 
            wid = self.get_rootwindow_serverlist()[servername]
374
 
        except KeyError:
375
 
            # The server is not registered in the root window so return None
376
 
            wid = None
377
 
        # Return wid if it is not none, or None
378
 
        return wid and long(wid) or None
379
 
 
380
 
    def get_server_window(self, wid):
381
 
        """
382
 
        Create and return a GDK window for a given window ID.
383
 
        
384
 
        This method simply calls gdk.window_foreign_new, which should return
385
 
        None if the window has been destroyed, but does not, in some cases.
386
 
 
387
 
        @param wid: The window ID.
388
 
        @type wid: long
389
 
        """
390
 
        return gtk.gdk.window_foreign_new(wid)
391
 
 
392
 
    def feed_serverlist(self, serverlist):
393
 
        """
394
 
        Feed the given list of servers to the client.
395
 
 
396
 
        This is achieved by calling the clients serverlist event. In Pida, this
397
 
        event is passed on to all the plugins.
398
 
 
399
 
        @param serverlist: The list of servers.
400
 
        @type serverlist: list
401
 
        """
402
 
        # Call the event.
403
 
        self.do_evt('serverlist', serverlist)
404
 
 
405
 
    def fetch_cwd(self, servername):
406
 
        """
407
 
        Fetch the working directory for a named server and store the result.
408
 
        """
409
 
        def gotcwd(cwd):
410
 
            """
411
 
            Called back on receiving the working directory, store it for later
412
 
            use.
413
 
            """
414
 
            self.server_cwds[servername] = cwd
415
 
        # Evaluate the expression with the gotcwd callback
416
 
        self.send_expr(servername, "getcwd()", gotcwd)
417
 
 
418
 
    def abspath(self, servername, filename):
419
 
        """
420
 
        Return the absolute path of a buffer name in the context of the named
421
 
        server.
422
 
        """
423
 
        # Only alter non-absolute paths
424
 
        if not filename.startswith('/'):
425
 
            try:
426
 
                # Try to find the current working directory
427
 
                cwd = self.server_cwds[servername]
428
 
            except KeyError:
429
 
                # The working directory is not set
430
 
                # Use a sane default, and fetch it
431
 
                cwd = os.path.expanduser('~')
432
 
                self.fetch_cwd(servername)
433
 
            filename = os.path.join(cwd, filename)
434
 
        return filename
435
 
 
436
 
    def generate_message(self, server, cork, message, sourceid):
437
 
        """
438
 
        Generate a message.
439
 
        """
440
 
        # Increment the serial number used for synchronous messages
441
 
        if cork:
442
 
            self.serial = self.serial + 1
443
 
            # Pick an arbitrary number where we recycle.
444
 
            if self.serial > 65530:
445
 
                self.serial = 1
446
 
        # return the generated string
447
 
        return '\0%s\0-n %s\0-s %s\0-r %x %s\0' % (cork,
448
 
                                                   server,
449
 
                                                   message,
450
 
                                                   sourceid,
451
 
                                                   self.serial)
452
 
 
453
 
    def parse_message(self, message):
454
 
        """
455
 
        Parse a received message and return the message atributes as a
456
 
        dictionary.
457
 
        """
458
 
        messageattrs = {}
459
 
        for t in [s.split(' ') for s in message.split('\0')]:
460
 
            if t and len(t[0]):
461
 
                if t[0].startswith('-'):
462
 
                    #attributes start with a '-', strip it and set the value
463
 
                    if len(t) > 1:
464
 
                        messageattrs[t[0][1:]] = t[1]
465
 
                else:
466
 
                    # Otherwise set the t attribute
467
 
                    messageattrs['t'] = t[0]
468
 
        return messageattrs
469
 
 
470
 
 
471
 
    def send_message(self, servername, message, asexpr, callback):
472
 
        wid = self.get_server_wid(servername)
473
 
        if wid:
474
 
            cork = (asexpr and 'c') or 'k'
475
 
            sw = self.get_server_window(wid)
476
 
            if sw and sw.property_get("Vim"):
477
 
                mp = self.generate_message(servername, cork, message,
478
 
                                        self.window.xid)
479
 
                sw.property_change("Comm", gdk.TARGET_STRING, 8,
480
 
                                        gdk.PROP_MODE_APPEND, mp)
481
 
                if asexpr and callback:
482
 
                    self.callbacks['%s' % (self.serial)] = callback
483
 
 
484
 
    def send_expr(self, server, message, callback):
485
 
        self.send_message(server, message, True, callback)
486
 
 
487
 
    def send_keys(self, server, message):
488
 
        self.send_message(server, message, False, False)
489
 
 
490
 
    def send_esc(self, server):
491
 
        self.send_keys(server, '<C-\><C-N>')
492
 
 
493
 
    def send_ret(self, server):
494
 
        self.send_keys(server, '<RETURN>')
495
 
 
496
 
    def send_ex(self, server, message):
497
 
        self.send_esc(server)
498
 
        self.send_keys(server, ':%s' % message)
499
 
        self.send_ret(server)
500
 
 
501
 
    def get_option(self, server, option, callbackfunc):
502
 
        self.send_expr(server, '&%s' % option, callbackfunc)
503
 
    
504
 
 
505
 
    def foreground(self, server):
506
 
        def cb(*args):
507
 
            pass
508
 
        self.send_expr(server, 'foreground()', cb)
509
 
        
510
 
    def change_buffer(self, server, nr):
511
 
        self.send_ex(server, 'b!%s' % nr)
512
 
 
513
 
    def close_buffer(self, server):
514
 
        self.send_ex(server, 'confirm bw')
515
 
 
516
 
    def change_cursor(self, server, x, y):
517
 
        self.send_message(server, 'cursor(%s, %s)' % (y, x), True, False)
518
 
        self.send_esc(server)
519
 
 
520
 
    def save_session(self, name):
521
 
        self.send_ex('mks %s' % name)
522
 
 
523
 
    def escape_filename(self, name):
524
 
        for s in ['\\', '?', '*', ' ', "'", '"', '[', ' ', '$']:
525
 
            name = name.replace (s, '\\%s' % s)
526
 
        return name
527
 
 
528
 
    def open_file(self, server, name):
529
 
        self.send_ex(server, 'confirm e %s' % self.escape_filename(name))
530
 
 
531
 
    def preview_file(self, server, fn):
532
 
        self.send_ex(server, 'pc')
533
 
        self.send_ex(server, 'set nopreviewwindow')
534
 
        self.send_ex(server, 'pedit %s' % fn)
535
 
 
536
 
    def get_bufferlist(self, server):
537
 
        def cb(bl):
538
 
            if bl:
539
 
                l = [i.split(':') for i in bl.strip(';').split(';')]
540
 
                L = []
541
 
                for n in l:
542
 
                    if not n[0].startswith('E'):
543
 
                        L.append([n[0], self.abspath(server, n[1])])
544
 
                self.do_evt('bufferlist', L)
545
 
        #self.get_cwd(server)
546
 
        self.send_expr(server, 'Bufferlist()', cb)
547
 
 
548
 
    def get_current_buffer(self, server):
549
 
        def cb(bs):
550
 
            bn = bs.split(',')
551
 
            bn[1] = self.abspath(server, bn[1])
552
 
            self.do_evt('bufferchange', *bn)
553
 
        #self.get_cwd(server)
554
 
        self.send_expr(server, "bufnr('%').','.bufname('%')", cb)
555
 
 
556
 
    def quit(self, server):
557
 
        self.send_ex(server, 'q')
558
 
 
559
 
    def cb_notify(self, *a):
560
 
        win, ev =  a
561
 
        if hasattr(ev, 'atom'):
562
 
            if ev.atom == 'Comm':
563
 
                message = self.window.property_get('Comm', pdelete=True)
564
 
                if message:
565
 
                    self.cb_reply(message[-1])
566
 
        return True
567
 
 
568
 
    def cb_reply(self, data):
569
 
        mdict = self.parse_message(data)
570
 
        if mdict['t'] == 'r':
571
 
            if mdict['s'] in self.callbacks:
572
 
                self.callbacks[mdict['s']](mdict['r'])
573
 
        else:
574
 
            s = [t for t in data.split('\0') if t.startswith('-n')].pop()[3:]
575
 
            self.cb_reply_async(s)
576
 
 
577
 
    def cb_reply_async(self, data):
578
 
        if data.count(','):
579
 
            evt, d = data.split(',', 1)
580
 
            self.do_evt(evt, *d.split(','))
581
 
        else:
582
 
            self.do_log('bad async reply', data, 10)
583