~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/conch/insults/insults.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2007-01-17 14:52:35 UTC
  • mfrom: (1.1.5 upstream) (2.1.2 etch)
  • Revision ID: james.westby@ubuntu.com-20070117145235-btmig6qfmqfen0om
Tags: 2.5.0-0ubuntu1
New upstream version, compatible with python2.5.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.conch.test.test_insults -*-
 
2
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
 
3
# See LICENSE for details.
 
4
 
 
5
"""VT102 terminal manipulation
 
6
 
 
7
API Stability: Unstable
 
8
 
 
9
@author: U{Jp Calderone<mailto:exarkun@twistedmatrix.com>}
 
10
"""
 
11
 
 
12
import string
 
13
 
 
14
from zope.interface import implements, Interface
 
15
 
 
16
from twisted.internet import protocol, defer, interfaces as iinternet
 
17
 
 
18
class ITerminalProtocol(Interface):
 
19
    def makeConnection(transport):
 
20
        """Called with an L{ITerminalTransport} when a connection is established.
 
21
        """
 
22
 
 
23
    def keystrokeReceived(keyID, modifier):
 
24
        """A keystroke was received.
 
25
 
 
26
        Each keystroke corresponds to one invocation of this method.
 
27
        keyID is a string identifier for that key.  Printable characters
 
28
        are represented by themselves.  Control keys, such as arrows and
 
29
        function keys, are represented with symbolic constants on
 
30
        L{ServerProtocol}.
 
31
        """
 
32
 
 
33
    def terminalSize(width, height):
 
34
        """Called to indicate the size of the terminal.
 
35
 
 
36
        A terminal of 80x24 should be assumed if this method is not
 
37
        called.  This method might not be called for real terminals.
 
38
        """
 
39
 
 
40
    def unhandledControlSequence(seq):
 
41
        """Called when an unsupported control sequence is received.
 
42
 
 
43
        @type seq: C{str}
 
44
        @param seq: The whole control sequence which could not be interpreted.
 
45
        """
 
46
 
 
47
    def connectionLost(reason):
 
48
        """Called when the connection has been lost.
 
49
 
 
50
        reason is a Failure describing why.
 
51
        """
 
52
 
 
53
class TerminalProtocol(object):
 
54
    implements(ITerminalProtocol)
 
55
 
 
56
    def makeConnection(self, terminal):
 
57
        # assert ITerminalTransport.providedBy(transport), "TerminalProtocol.makeConnection must be passed an ITerminalTransport implementor"
 
58
        self.terminal = terminal
 
59
        self.connectionMade()
 
60
 
 
61
    def connectionMade(self):
 
62
        """Called after a connection has been established.
 
63
        """
 
64
 
 
65
    def keystrokeReceived(self, keyID, modifier):
 
66
        pass
 
67
 
 
68
    def terminalSize(self, width, height):
 
69
        pass
 
70
 
 
71
    def unhandledControlSequence(self, seq):
 
72
        pass
 
73
 
 
74
    def connectionLost(self, reason):
 
75
        pass
 
76
 
 
77
class ITerminalTransport(iinternet.ITransport):
 
78
    def cursorUp(n=1):
 
79
        """Move the cursor up n lines.
 
80
        """
 
81
 
 
82
    def cursorDown(n=1):
 
83
        """Move the cursor down n lines.
 
84
        """
 
85
 
 
86
    def cursorForward(n=1):
 
87
        """Move the cursor right n columns.
 
88
        """
 
89
 
 
90
    def cursorBackward(n=1):
 
91
        """Move the cursor left n columns.
 
92
        """
 
93
 
 
94
    def cursorPosition(column, line):
 
95
        """Move the cursor to the given line and column.
 
96
        """
 
97
 
 
98
    def cursorHome():
 
99
        """Move the cursor home.
 
100
        """
 
101
 
 
102
    def index():
 
103
        """Move the cursor down one line, performing scrolling if necessary.
 
104
        """
 
105
 
 
106
    def reverseIndex():
 
107
        """Move the cursor up one line, performing scrolling if necessary.
 
108
        """
 
109
 
 
110
    def nextLine():
 
111
        """Move the cursor to the first position on the next line, performing scrolling if necessary.
 
112
        """
 
113
 
 
114
    def saveCursor():
 
115
        """Save the cursor position, character attribute, character set, and origin mode selection.
 
116
        """
 
117
 
 
118
    def restoreCursor():
 
119
        """Restore the previously saved cursor position, character attribute, character set, and origin mode selection.
 
120
 
 
121
        If no cursor state was previously saved, move the cursor to the home position.
 
122
        """
 
123
 
 
124
    def setModes(modes):
 
125
        """Set the given modes on the terminal.
 
126
        """
 
127
 
 
128
    def resetModes(mode):
 
129
        """Reset the given modes on the terminal.
 
130
        """
 
131
 
 
132
    def applicationKeypadMode():
 
133
        """Cause keypad to generate control functions.
 
134
 
 
135
        Cursor key mode selects the type of characters generated by cursor keys.
 
136
        """
 
137
 
 
138
    def numericKeypadMode():
 
139
        """Cause keypad to generate normal characters.
 
140
        """
 
141
 
 
142
    def selectCharacterSet(charSet, which):
 
143
        """Select a character set.
 
144
 
 
145
        charSet should be one of CS_US, CS_UK, CS_DRAWING, CS_ALTERNATE, or
 
146
        CS_ALTERNATE_SPECIAL.
 
147
 
 
148
        which should be one of G0 or G1.
 
149
        """
 
150
 
 
151
    def shiftIn():
 
152
        """Activate the G0 character set.
 
153
        """
 
154
 
 
155
    def shiftOut():
 
156
        """Activate the G1 character set.
 
157
        """
 
158
 
 
159
    def singleShift2():
 
160
        """Shift to the G2 character set for a single character.
 
161
        """
 
162
 
 
163
    def singleShift3():
 
164
        """Shift to the G3 character set for a single character.
 
165
        """
 
166
 
 
167
    def selectGraphicRendition(*attributes):
 
168
        """Enabled one or more character attributes.
 
169
 
 
170
        Arguments should be one or more of UNDERLINE, REVERSE_VIDEO, BLINK, or BOLD.
 
171
        NORMAL may also be specified to disable all character attributes.
 
172
        """
 
173
 
 
174
    def horizontalTabulationSet():
 
175
        """Set a tab stop at the current cursor position.
 
176
        """
 
177
 
 
178
    def tabulationClear():
 
179
        """Clear the tab stop at the current cursor position.
 
180
        """
 
181
 
 
182
    def tabulationClearAll():
 
183
        """Clear all tab stops.
 
184
        """
 
185
 
 
186
    def doubleHeightLine(top=True):
 
187
        """Make the current line the top or bottom half of a double-height, double-width line.
 
188
 
 
189
        If top is True, the current line is the top half.  Otherwise, it is the bottom half.
 
190
        """
 
191
 
 
192
    def singleWidthLine():
 
193
        """Make the current line a single-width, single-height line.
 
194
        """
 
195
 
 
196
    def doubleWidthLine():
 
197
        """Make the current line a double-width line.
 
198
        """
 
199
 
 
200
    def eraseToLineEnd():
 
201
        """Erase from the cursor to the end of line, including cursor position.
 
202
        """
 
203
 
 
204
    def eraseToLineBeginning():
 
205
        """Erase from the cursor to the beginning of the line, including the cursor position.
 
206
        """
 
207
 
 
208
    def eraseLine():
 
209
        """Erase the entire cursor line.
 
210
        """
 
211
 
 
212
    def eraseToDisplayEnd():
 
213
        """Erase from the cursor to the end of the display, including the cursor position.
 
214
        """
 
215
 
 
216
    def eraseToDisplayBeginning():
 
217
        """Erase from the cursor to the beginning of the display, including the cursor position.
 
218
        """
 
219
 
 
220
    def eraseDisplay():
 
221
        """Erase the entire display.
 
222
        """
 
223
 
 
224
    def deleteCharacter(n=1):
 
225
        """Delete n characters starting at the cursor position.
 
226
 
 
227
        Characters to the right of deleted characters are shifted to the left.
 
228
        """
 
229
 
 
230
    def insertLine(n=1):
 
231
        """Insert n lines at the cursor position.
 
232
 
 
233
        Lines below the cursor are shifted down.  Lines moved past the bottom margin are lost.
 
234
        This command is ignored when the cursor is outside the scroll region.
 
235
        """
 
236
 
 
237
    def deleteLine(n=1):
 
238
        """Delete n lines starting at the cursor position.
 
239
 
 
240
        Lines below the cursor are shifted up.  This command is ignored when the cursor is outside
 
241
        the scroll region.
 
242
        """
 
243
 
 
244
    def reportCursorPosition():
 
245
        """Return a Deferred that fires with a two-tuple of (x, y) indicating the cursor position.
 
246
        """
 
247
 
 
248
    def reset():
 
249
        """Reset the terminal to its initial state.
 
250
        """
 
251
 
 
252
    def unhandledControlSequence(seq):
 
253
        """Called when an unsupported control sequence is received.
 
254
 
 
255
        @type seq: C{str}
 
256
        @param seq: The whole control sequence which could not be interpreted.
 
257
        """
 
258
 
 
259
 
 
260
CSI = '\x1b'
 
261
CST = {'~': 'tilde'}
 
262
 
 
263
class modes:
 
264
    """ECMA 48 standardized modes
 
265
    """
 
266
 
 
267
    # BREAKS YOPUR KEYBOARD MOFO
 
268
    KEYBOARD_ACTION = KAM = 2
 
269
 
 
270
    # When set, enables character insertion. New display characters
 
271
    # move old display characters to the right. Characters moved past
 
272
    # the right margin are lost.
 
273
 
 
274
    # When reset, enables replacement mode (disables character
 
275
    # insertion). New display characters replace old display
 
276
    # characters at cursor position. The old character is erased.
 
277
    INSERTION_REPLACEMENT = IRM = 4
 
278
 
 
279
    # Set causes a received linefeed, form feed, or vertical tab to
 
280
    # move cursor to first column of next line. RETURN transmits both
 
281
    # a carriage return and linefeed. This selection is also called
 
282
    # new line option.
 
283
 
 
284
    # Reset causes a received linefeed, form feed, or vertical tab to
 
285
    # move cursor to next line in current column. RETURN transmits a
 
286
    # carriage return.
 
287
    LINEFEED_NEWLINE = LNM = 20
 
288
 
 
289
 
 
290
class privateModes:
 
291
    """ANSI-Compatible Private Modes
 
292
    """
 
293
    ERROR = 0
 
294
    CURSOR_KEY = 1
 
295
    ANSI_VT52 = 2
 
296
    COLUMN = 3
 
297
    SCROLL = 4
 
298
    SCREEN = 5
 
299
    ORIGIN = 6
 
300
    AUTO_WRAP = 7
 
301
    AUTO_REPEAT = 8
 
302
    PRINTER_FORM_FEED = 18
 
303
    PRINTER_EXTENT = 19
 
304
 
 
305
    # Toggle cursor visibility (reset hides it)
 
306
    CURSOR_MODE = 25
 
307
 
 
308
 
 
309
# Character sets
 
310
CS_US = 'CS_US'
 
311
CS_UK = 'CS_UK'
 
312
CS_DRAWING = 'CS_DRAWING'
 
313
CS_ALTERNATE = 'CS_ALTERNATE'
 
314
CS_ALTERNATE_SPECIAL = 'CS_ALTERNATE_SPECIAL'
 
315
 
 
316
# Groupings (or something?? These are like variables that can be bound to character sets)
 
317
G0 = 'G0'
 
318
G1 = 'G1'
 
319
 
 
320
# G2 and G3 cannot be changed, but they can be shifted to.
 
321
G2 = 'G2'
 
322
G3 = 'G3'
 
323
 
 
324
# Character attributes
 
325
 
 
326
NORMAL = 0
 
327
BOLD = 1
 
328
UNDERLINE = 4
 
329
BLINK = 5
 
330
REVERSE_VIDEO = 7
 
331
 
 
332
class Vector:
 
333
    def __init__(self, x, y):
 
334
        self.x = x
 
335
        self.y = y
 
336
 
 
337
def log(s):
 
338
    file('log', 'a').write(str(s) + '\n')
 
339
 
 
340
# XXX TODO - These attributes are really part of the
 
341
# ITerminalTransport interface, I think.
 
342
_KEY_NAMES = ('UP_ARROW', 'DOWN_ARROW', 'RIGHT_ARROW', 'LEFT_ARROW',
 
343
              'HOME', 'INSERT', 'DELETE', 'END', 'PGUP', 'PGDN', 'NUMPAD_MIDDLE',
 
344
              'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9',
 
345
              'F10', 'F11', 'F12',
 
346
 
 
347
              'ALT', 'SHIFT', 'CONTROL')
 
348
 
 
349
class _const(object):
 
350
    """
 
351
    @ivar name: A string naming this constant
 
352
    """
 
353
    def __init__(self, name):
 
354
        self.name = name
 
355
 
 
356
    def __repr__(self):
 
357
        return '[' + self.name + ']'
 
358
 
 
359
 
 
360
FUNCTION_KEYS = [
 
361
    _const(_name) for _name in _KEY_NAMES]
 
362
 
 
363
class ServerProtocol(protocol.Protocol):
 
364
    implements(ITerminalTransport)
 
365
 
 
366
    protocolFactory = None
 
367
    terminalProtocol = None
 
368
 
 
369
    TAB = '\t'
 
370
    BACKSPACE = '\x7f'
 
371
    ##
 
372
 
 
373
    lastWrite = ''
 
374
 
 
375
    state = 'data'
 
376
 
 
377
    termSize = Vector(80, 24)
 
378
    cursorPos = Vector(0, 0)
 
379
    scrollRegion = None
 
380
 
 
381
    # Factory who instantiated me
 
382
    factory = None
 
383
 
 
384
    def __init__(self, protocolFactory=None, *a, **kw):
 
385
        """
 
386
        @param protocolFactory: A callable which will be invoked with
 
387
        *a, **kw and should return an ITerminalProtocol implementor.
 
388
        This will be invoked when a connection to this ServerProtocol
 
389
        is established.
 
390
 
 
391
        @param a: Any positional arguments to pass to protocolFactory.
 
392
        @param kw: Any keyword arguments to pass to protocolFactory.
 
393
        """
 
394
        # assert protocolFactory is None or ITerminalProtocol.implementedBy(protocolFactory), "ServerProtocol.__init__ must be passed an ITerminalProtocol implementor"
 
395
        if protocolFactory is not None:
 
396
            self.protocolFactory = protocolFactory
 
397
        self.protocolArgs = a
 
398
        self.protocolKwArgs = kw
 
399
 
 
400
        self._cursorReports = []
 
401
 
 
402
    def connectionMade(self):
 
403
        if self.protocolFactory is not None:
 
404
            self.terminalProtocol = self.protocolFactory(*self.protocolArgs, **self.protocolKwArgs)
 
405
 
 
406
            try:
 
407
                factory = self.factory
 
408
            except AttributeError:
 
409
                pass
 
410
            else:
 
411
                self.terminalProtocol.factory = factory
 
412
 
 
413
            self.terminalProtocol.makeConnection(self)
 
414
 
 
415
    def dataReceived(self, data):
 
416
        for ch in data:
 
417
            if self.state == 'data':
 
418
                if ch == '\x1b':
 
419
                    self.state = 'escaped'
 
420
                else:
 
421
                    self.terminalProtocol.keystrokeReceived(ch, None)
 
422
            elif self.state == 'escaped':
 
423
                if ch == '[':
 
424
                    self.state = 'bracket-escaped'
 
425
                    self.escBuf = []
 
426
                elif ch == 'O':
 
427
                    self.state = 'low-function-escaped'
 
428
                else:
 
429
                    self.state = 'data'
 
430
                    self._handleShortControlSequence(ch)
 
431
            elif self.state == 'bracket-escaped':
 
432
                if ch == 'O':
 
433
                    self.state = 'low-function-escaped'
 
434
                elif ch.isalpha() or ch == '~':
 
435
                    self._handleControlSequence(''.join(self.escBuf) + ch)
 
436
                    del self.escBuf
 
437
                    self.state = 'data'
 
438
                else:
 
439
                    self.escBuf.append(ch)
 
440
            elif self.state == 'low-function-escaped':
 
441
                self._handleLowFunctionControlSequence(ch)
 
442
                self.state = 'data'
 
443
            else:
 
444
                raise ValueError("Illegal state")
 
445
 
 
446
    def _handleShortControlSequence(self, ch):
 
447
        self.terminalProtocol.keystrokeReceived(ch, self.ALT)
 
448
 
 
449
    def _handleControlSequence(self, buf):
 
450
        buf = '\x1b[' + buf
 
451
        f = getattr(self.controlSequenceParser, CST.get(buf[-1], buf[-1]), None)
 
452
        if f is None:
 
453
            self.unhandledControlSequence(buf)
 
454
        else:
 
455
            f(self, self.terminalProtocol, buf[:-1])
 
456
 
 
457
    def unhandledControlSequence(self, buf):
 
458
        self.terminalProtocol.unhandledControlSequence(buf)
 
459
 
 
460
    def _handleLowFunctionControlSequence(self, ch):
 
461
        map = {'P': self.F1, 'Q': self.F2, 'R': self.F3, 'S': self.F4}
 
462
        keyID = map.get(ch)
 
463
        if keyID is not None:
 
464
            self.terminalProtocol.keystrokeReceived(keyID, None)
 
465
        else:
 
466
            self.terminalProtocol.unhandledControlSequence('\x1b[O' + ch)
 
467
 
 
468
    class ControlSequenceParser:
 
469
        def A(self, proto, handler, buf):
 
470
            if buf == '\x1b[':
 
471
                handler.keystrokeReceived(proto.UP_ARROW, None)
 
472
            else:
 
473
                handler.unhandledControlSequence(buf + 'A')
 
474
 
 
475
        def B(self, proto, handler, buf):
 
476
            if buf == '\x1b[':
 
477
                handler.keystrokeReceived(proto.DOWN_ARROW, None)
 
478
            else:
 
479
                handler.unhandledControlSequence(buf + 'B')
 
480
 
 
481
        def C(self, proto, handler, buf):
 
482
            if buf == '\x1b[':
 
483
                handler.keystrokeReceived(proto.RIGHT_ARROW, None)
 
484
            else:
 
485
                handler.unhandledControlSequence(buf + 'C')
 
486
 
 
487
        def D(self, proto, handler, buf):
 
488
            if buf == '\x1b[':
 
489
                handler.keystrokeReceived(proto.LEFT_ARROW, None)
 
490
            else:
 
491
                handler.unhandledControlSequence(buf + 'D')
 
492
 
 
493
        def E(self, proto, handler, buf):
 
494
            if buf == '\x1b[':
 
495
                handler.keystrokeReceived(proto.NUMPAD_MIDDLE, None)
 
496
            else:
 
497
                handler.unhandledControlSequence(buf + 'E')
 
498
 
 
499
        def F(self, proto, handler, buf):
 
500
            if buf == '\x1b[':
 
501
                handler.keystrokeReceived(proto.END, None)
 
502
            else:
 
503
                handler.unhandledControlSequence(buf + 'F')
 
504
 
 
505
        def H(self, proto, handler, buf):
 
506
            if buf == '\x1b[':
 
507
                handler.keystrokeReceived(proto.HOME, None)
 
508
            else:
 
509
                handler.unhandledControlSequence(buf + 'H')
 
510
 
 
511
        def R(self, proto, handler, buf):
 
512
            if not proto._cursorReports:
 
513
                handler.unhandledControlSequence(buf + 'R')
 
514
            elif buf.startswith('\x1b['):
 
515
                report = buf[2:]
 
516
                parts = report.split(';')
 
517
                if len(parts) != 2:
 
518
                    handler.unhandledControlSequence(buf + 'R')
 
519
                else:
 
520
                    Pl, Pc = parts
 
521
                    try:
 
522
                        Pl, Pc = int(Pl), int(Pc)
 
523
                    except ValueError:
 
524
                        handler.unhandledControlSequence(buf + 'R')
 
525
                    else:
 
526
                        d = proto._cursorReports.pop(0)
 
527
                        d.callback((Pc - 1, Pl - 1))
 
528
            else:
 
529
                handler.unhandledControlSequence(buf + 'R')
 
530
 
 
531
        def Z(self, proto, handler, buf):
 
532
            if buf == '\x1b[':
 
533
                handler.keystrokeReceived(proto.TAB, proto.SHIFT)
 
534
            else:
 
535
                handler.unhandledControlSequence(buf + 'Z')
 
536
 
 
537
        def tilde(self, proto, handler, buf):
 
538
            map = {1: proto.HOME, 2: proto.INSERT, 3: proto.DELETE,
 
539
                   4: proto.END,  5: proto.PGUP,   6: proto.PGDN,
 
540
 
 
541
                   15: proto.F5,  17: proto.F6, 18: proto.F7,
 
542
                   19: proto.F8,  20: proto.F9, 21: proto.F10,
 
543
                   23: proto.F11, 24: proto.F12}
 
544
 
 
545
            if buf.startswith('\x1b['):
 
546
                ch = buf[2:]
 
547
                try:
 
548
                    v = int(ch)
 
549
                except ValueError:
 
550
                    handler.unhandledControlSequence(buf + '~')
 
551
                else:
 
552
                    symbolic = map.get(v)
 
553
                    if symbolic is not None:
 
554
                        handler.keystrokeReceived(map[v], None)
 
555
                    else:
 
556
                        handler.unhandledControlSequence(buf + '~')
 
557
            else:
 
558
                handler.unhandledControlSequence(buf + '~')
 
559
 
 
560
    controlSequenceParser = ControlSequenceParser()
 
561
 
 
562
    # ITerminalTransport
 
563
    def cursorUp(self, n=1):
 
564
        assert n >= 1
 
565
        self.cursorPos.y = max(self.cursorPos.y - n, 0)
 
566
        self.write('\x1b[%dA' % (n,))
 
567
 
 
568
    def cursorDown(self, n=1):
 
569
        assert n >= 1
 
570
        self.cursorPos.y = min(self.cursorPos.y + n, self.termSize.y - 1)
 
571
        self.write('\x1b[%dB' % (n,))
 
572
 
 
573
    def cursorForward(self, n=1):
 
574
        assert n >= 1
 
575
        self.cursorPos.x = min(self.cursorPos.x + n, self.termSize.x - 1)
 
576
        self.write('\x1b[%dC' % (n,))
 
577
 
 
578
    def cursorBackward(self, n=1):
 
579
        assert n >= 1
 
580
        self.cursorPos.x = max(self.cursorPos.x - n, 0)
 
581
        self.write('\x1b[%dD' % (n,))
 
582
 
 
583
    def cursorPosition(self, column, line):
 
584
        self.write('\x1b[%d;%dH' % (line + 1, column + 1))
 
585
 
 
586
    def cursorHome(self):
 
587
        self.cursorPos.x = self.cursorPos.y = 0
 
588
        self.write('\x1b[H')
 
589
 
 
590
    def index(self):
 
591
        self.cursorPos.y = min(self.cursorPos.y + 1, self.termSize.y - 1)
 
592
        self.write('\x1bD')
 
593
 
 
594
    def reverseIndex(self):
 
595
        self.cursorPos.y = max(self.cursorPos.y - 1, 0)
 
596
        self.write('\x1bM')
 
597
 
 
598
    def nextLine(self):
 
599
        self.cursorPos.x = 0
 
600
        self.cursorPos.y = min(self.cursorPos.y + 1, self.termSize.y - 1)
 
601
        self.write('\x1bE')
 
602
 
 
603
    def saveCursor(self):
 
604
        self._savedCursorPos = Vector(self.cursorPos.x, self.cursorPos.y)
 
605
        self.write('\x1b7')
 
606
 
 
607
    def restoreCursor(self):
 
608
        self.cursorPos = self._savedCursorPos
 
609
        del self._savedCursorPos
 
610
        self.write('\x1b8')
 
611
 
 
612
    def setModes(self, modes):
 
613
        # XXX Support ANSI-Compatible private modes
 
614
        self.write('\x1b[%sh' % (';'.join(map(str, modes)),))
 
615
 
 
616
    def setPrivateModes(self, modes):
 
617
        self.write('\x1b[?%sh' % (';'.join(map(str, modes)),))
 
618
 
 
619
    def resetModes(self, modes):
 
620
        # XXX Support ANSI-Compatible private modes
 
621
        self.write('\x1b[%sl' % (';'.join(map(str, modes)),))
 
622
 
 
623
    def resetPrivateModes(self, modes):
 
624
        self.write('\x1b[?%sl' % (';'.join(map(str, modes)),))
 
625
 
 
626
    def applicationKeypadMode(self):
 
627
        self.write('\x1b=')
 
628
 
 
629
    def numericKeypadMode(self):
 
630
        self.write('\x1b>')
 
631
 
 
632
    def selectCharacterSet(self, charSet, which):
 
633
        # XXX Rewrite these as dict lookups
 
634
        if which == G0:
 
635
            which = '('
 
636
        elif which == G1:
 
637
            which = ')'
 
638
        else:
 
639
            raise ValueError("`which' argument to selectCharacterSet must be G0 or G1")
 
640
        if charSet == CS_UK:
 
641
            charSet = 'A'
 
642
        elif charSet == CS_US:
 
643
            charSet = 'B'
 
644
        elif charSet == CS_DRAWING:
 
645
            charSet = '0'
 
646
        elif charSet == CS_ALTERNATE:
 
647
            charSet = '1'
 
648
        elif charSet == CS_ALTERNATE_SPECIAL:
 
649
            charSet = '2'
 
650
        else:
 
651
            raise ValueError("Invalid `charSet' argument to selectCharacterSet")
 
652
        self.write('\x1b' + which + charSet)
 
653
 
 
654
    def shiftIn(self):
 
655
        self.write('\x15')
 
656
 
 
657
    def shiftOut(self):
 
658
        self.write('\x14')
 
659
 
 
660
    def singleShift2(self):
 
661
        self.write('\x1bN')
 
662
 
 
663
    def singleShift3(self):
 
664
        self.write('\x1bO')
 
665
 
 
666
    def selectGraphicRendition(self, *attributes):
 
667
        attrs = []
 
668
        for a in attributes:
 
669
            attrs.append(a)
 
670
        self.write('\x1b[%sm' % (';'.join(attrs),))
 
671
 
 
672
    def horizontalTabulationSet(self):
 
673
        self.write('\x1bH')
 
674
 
 
675
    def tabulationClear(self):
 
676
        self.write('\x1b[q')
 
677
 
 
678
    def tabulationClearAll(self):
 
679
        self.write('\x1b[3q')
 
680
 
 
681
    def doubleHeightLine(self, top=True):
 
682
        if top:
 
683
            self.write('\x1b#3')
 
684
        else:
 
685
            self.write('\x1b#4')
 
686
 
 
687
    def singleWidthLine(self):
 
688
        self.write('\x1b#5')
 
689
 
 
690
    def doubleWidthLine(self):
 
691
        self.write('\x1b#6')
 
692
 
 
693
    def eraseToLineEnd(self):
 
694
        self.write('\x1b[K')
 
695
 
 
696
    def eraseToLineBeginning(self):
 
697
        self.write('\x1b[1K')
 
698
 
 
699
    def eraseLine(self):
 
700
        self.write('\x1b[2K')
 
701
 
 
702
    def eraseToDisplayEnd(self):
 
703
        self.write('\x1b[J')
 
704
 
 
705
    def eraseToDisplayBeginning(self):
 
706
        self.write('\x1b[1J')
 
707
 
 
708
    def eraseDisplay(self):
 
709
        self.write('\x1b[2J')
 
710
 
 
711
    def deleteCharacter(self, n=1):
 
712
        self.write('\x1b[%dP' % (n,))
 
713
 
 
714
    def insertLine(self, n=1):
 
715
        self.write('\x1b[%dL' % (n,))
 
716
 
 
717
    def deleteLine(self, n=1):
 
718
        self.write('\x1b[%dM' % (n,))
 
719
 
 
720
    def setScrollRegion(self, first=None, last=None):
 
721
        if first is not None:
 
722
            first = '%d' % (first,)
 
723
        else:
 
724
            first = ''
 
725
        if last is not None:
 
726
            last = '%d' % (last,)
 
727
        else:
 
728
            last = ''
 
729
        self.write('\x1b[%s;%sr' % (first, last))
 
730
 
 
731
    def resetScrollRegion(self):
 
732
        self.setScrollRegion()
 
733
 
 
734
    def reportCursorPosition(self):
 
735
        d = defer.Deferred()
 
736
        self._cursorReports.append(d)
 
737
        self.write('\x1b[6n')
 
738
        return d
 
739
 
 
740
    def reset(self):
 
741
        self.cursorPos.x = self.cursorPos.y = 0
 
742
        try:
 
743
            del self._savedCursorPos
 
744
        except AttributeError:
 
745
            pass
 
746
        self.write('\x1bc')
 
747
 
 
748
    # ITransport
 
749
    def write(self, bytes):
 
750
        if bytes:
 
751
            self.lastWrite = bytes
 
752
            self.transport.write('\r\n'.join(bytes.split('\n')))
 
753
 
 
754
    def writeSequence(self, bytes):
 
755
        self.write(''.join(bytes))
 
756
 
 
757
    def loseConnection(self):
 
758
        self.reset()
 
759
        self.transport.loseConnection()
 
760
 
 
761
    def connectionLost(self, reason):
 
762
        if self.terminalProtocol is not None:
 
763
            try:
 
764
                self.terminalProtocol.connectionLost(reason)
 
765
            finally:
 
766
                self.terminalProtocol = None
 
767
# Add symbolic names for function keys
 
768
for name, const in zip(_KEY_NAMES, FUNCTION_KEYS):
 
769
    setattr(ServerProtocol, name, const)
 
770
 
 
771
 
 
772
 
 
773
class ClientProtocol(protocol.Protocol):
 
774
 
 
775
    terminalFactory = None
 
776
    terminal = None
 
777
 
 
778
    state = 'data'
 
779
 
 
780
    _escBuf = None
 
781
 
 
782
    _shorts = {
 
783
        'D': 'index',
 
784
        'M': 'reverseIndex',
 
785
        'E': 'nextLine',
 
786
        '7': 'saveCursor',
 
787
        '8': 'restoreCursor',
 
788
        '=': 'applicationKeypadMode',
 
789
        '>': 'numericKeypadMode',
 
790
        'N': 'singleShift2',
 
791
        'O': 'singleShift3',
 
792
        'H': 'horizontalTabulationSet',
 
793
        'c': 'reset'}
 
794
 
 
795
    _longs = {
 
796
        '[': 'bracket-escape',
 
797
        '(': 'select-g0',
 
798
        ')': 'select-g1',
 
799
        '#': 'select-height-width'}
 
800
 
 
801
    _charsets = {
 
802
        'A': CS_UK,
 
803
        'B': CS_US,
 
804
        '0': CS_DRAWING,
 
805
        '1': CS_ALTERNATE,
 
806
        '2': CS_ALTERNATE_SPECIAL}
 
807
 
 
808
    # Factory who instantiated me
 
809
    factory = None
 
810
 
 
811
    def __init__(self, terminalFactory=None, *a, **kw):
 
812
        """
 
813
        @param terminalFactory: A callable which will be invoked with
 
814
        *a, **kw and should return an ITerminalTransport provider.
 
815
        This will be invoked when this ClientProtocol establishes a
 
816
        connection.
 
817
 
 
818
        @param a: Any positional arguments to pass to terminalFactory.
 
819
        @param kw: Any keyword arguments to pass to terminalFactory.
 
820
        """
 
821
        # assert terminalFactory is None or ITerminalTransport.implementedBy(terminalFactory), "ClientProtocol.__init__ must be passed an ITerminalTransport implementor"
 
822
        if terminalFactory is not None:
 
823
            self.terminalFactory = terminalFactory
 
824
        self.terminalArgs = a
 
825
        self.terminalKwArgs = kw
 
826
 
 
827
    def connectionMade(self):
 
828
        if self.terminalFactory is not None:
 
829
            self.terminal = self.terminalFactory(*self.terminalArgs, **self.terminalKwArgs)
 
830
            self.terminal.factory = self.factory
 
831
            self.terminal.makeConnection(self)
 
832
 
 
833
    def connectionLost(self, reason):
 
834
        if self.terminal is not None:
 
835
            try:
 
836
                self.terminal.connectionLost(reason)
 
837
            finally:
 
838
                del self.terminal
 
839
 
 
840
    def dataReceived(self, bytes):
 
841
        for b in bytes:
 
842
            if self.state == 'data':
 
843
                if b == '\x1b':
 
844
                    self.state = 'escaped'
 
845
                elif b == '\x14':
 
846
                    self.terminal.shiftOut()
 
847
                elif b == '\x15':
 
848
                    self.terminal.shiftIn()
 
849
                elif b == '\x08':
 
850
                    self.terminal.cursorBackward()
 
851
                else:
 
852
                    self.terminal.write(b)
 
853
            elif self.state == 'escaped':
 
854
                fName = self._shorts.get(b)
 
855
                if fName is not None:
 
856
                    self.state = 'data'
 
857
                    getattr(self.terminal, fName)()
 
858
                else:
 
859
                    state = self._longs.get(b)
 
860
                    if state is not None:
 
861
                        self.state = state
 
862
                    else:
 
863
                        self.terminal.unhandledControlSequence('\x1b' + b)
 
864
                        self.state = 'data'
 
865
            elif self.state == 'bracket-escape':
 
866
                if self._escBuf is None:
 
867
                    self._escBuf = []
 
868
                if b.isalpha() or b == '~':
 
869
                    self._handleControlSequence(''.join(self._escBuf), b)
 
870
                    del self._escBuf
 
871
                    self.state = 'data'
 
872
                else:
 
873
                    self._escBuf.append(b)
 
874
            elif self.state == 'select-g0':
 
875
                self.terminal.selectCharacterSet(self._charsets.get(b, b), G0)
 
876
                self.state = 'data'
 
877
            elif self.state == 'select-g1':
 
878
                self.terminal.selectCharacterSet(self._charsets.get(b, b), G1)
 
879
                self.state = 'data'
 
880
            elif self.state == 'select-height-width':
 
881
                self._handleHeightWidth(b)
 
882
                self.state = 'data'
 
883
            else:
 
884
                raise ValueError("Illegal state")
 
885
 
 
886
    def _handleControlSequence(self, buf, terminal):
 
887
        f = getattr(self.controlSequenceParser, CST.get(terminal, terminal), None)
 
888
        if f is None:
 
889
            self.terminal.unhandledControlSequence('\x1b[' + buf + terminal)
 
890
        else:
 
891
            f(self, self.terminal, buf)
 
892
 
 
893
    class ControlSequenceParser:
 
894
        def _makeSimple(ch, fName):
 
895
            n = 'cursor' + fName
 
896
            def simple(self, proto, handler, buf):
 
897
                if not buf:
 
898
                    getattr(handler, n)(1)
 
899
                else:
 
900
                    try:
 
901
                        m = int(buf)
 
902
                    except ValueError:
 
903
                        handler.unhandledControlSequence('\x1b[' + buf + ch)
 
904
                    else:
 
905
                        getattr(handler, n)(m)
 
906
            return simple
 
907
        for (ch, fName) in (('A', 'Up'),
 
908
                            ('B', 'Down'),
 
909
                            ('C', 'Forward'),
 
910
                            ('D', 'Backward')):
 
911
            exec ch + " = _makeSimple(ch, fName)"
 
912
        del _makeSimple
 
913
 
 
914
        def h(self, proto, handler, buf):
 
915
            # XXX - Handle '?' to introduce ANSI-Compatible private modes.
 
916
            try:
 
917
                modes = map(int, buf.split(';'))
 
918
            except ValueError:
 
919
                handler.unhandledControlSequence('\x1b[' + buf + 'h')
 
920
            else:
 
921
                handler.setModes(modes)
 
922
 
 
923
        def l(self, proto, handler, buf):
 
924
            # XXX - Handle '?' to introduce ANSI-Compatible private modes.
 
925
            try:
 
926
                modes = map(int, buf.split(';'))
 
927
            except ValueError:
 
928
                handler.unhandledControlSequence('\x1b[' + buf + 'l')
 
929
            else:
 
930
                handler.resetModes(modes)
 
931
 
 
932
        def r(self, proto, handler, buf):
 
933
            parts = buf.split(';')
 
934
            if len(parts) == 1:
 
935
                handler.setScrollRegion(None, None)
 
936
            elif len(parts) == 2:
 
937
                try:
 
938
                    if parts[0]:
 
939
                        pt = int(parts[0])
 
940
                    else:
 
941
                        pt = None
 
942
                    if parts[1]:
 
943
                        pb = int(parts[1])
 
944
                    else:
 
945
                        pb = None
 
946
                except ValueError:
 
947
                    handler.unhandledControlSequence('\x1b[' + buf + 'r')
 
948
                else:
 
949
                    handler.setScrollRegion(pt, pb)
 
950
            else:
 
951
                handler.unhandledControlSequence('\x1b[' + buf + 'r')
 
952
 
 
953
        def K(self, proto, handler, buf):
 
954
            if not buf:
 
955
                handler.eraseToLineEnd()
 
956
            elif buf == '1':
 
957
                handler.eraseToLineBeginning()
 
958
            elif buf == '2':
 
959
                handler.eraseLine()
 
960
            else:
 
961
                handler.unhandledControlSequence('\x1b[' + buf + 'K')
 
962
 
 
963
        def H(self, proto, handler, buf):
 
964
            handler.cursorHome()
 
965
 
 
966
        def J(self, proto, handler, buf):
 
967
            if not buf:
 
968
                handler.eraseToDisplayEnd()
 
969
            elif buf == '1':
 
970
                handler.eraseToDisplayBeginning()
 
971
            elif buf == '2':
 
972
                handler.eraseDisplay()
 
973
            else:
 
974
                handler.unhandledControlSequence('\x1b[' + buf + 'J')
 
975
 
 
976
        def P(self, proto, handler, buf):
 
977
            if not buf:
 
978
                handler.deleteCharacter(1)
 
979
            else:
 
980
                try:
 
981
                    n = int(buf)
 
982
                except ValueError:
 
983
                    handler.unhandledControlSequence('\x1b[' + buf + 'P')
 
984
                else:
 
985
                    handler.deleteCharacter(n)
 
986
 
 
987
        def L(self, proto, handler, buf):
 
988
            if not buf:
 
989
                handler.insertLine(1)
 
990
            else:
 
991
                try:
 
992
                    n = int(buf)
 
993
                except ValueError:
 
994
                    handler.unhandledControlSequence('\x1b[' + buf + 'L')
 
995
                else:
 
996
                    handler.insertLine(n)
 
997
 
 
998
        def M(self, proto, handler, buf):
 
999
            if not buf:
 
1000
                handler.deleteLine(1)
 
1001
            else:
 
1002
                try:
 
1003
                    n = int(buf)
 
1004
                except ValueError:
 
1005
                    handler.unhandledControlSequence('\x1b[' + buf + 'M')
 
1006
                else:
 
1007
                    handler.deleteLine(n)
 
1008
 
 
1009
        def n(self, proto, handler, buf):
 
1010
            if buf == '6':
 
1011
                x, y = handler.reportCursorPosition()
 
1012
                proto.transport.write('\x1b[%d;%dR' % (x + 1, y + 1))
 
1013
            else:
 
1014
                handler.unhandledControlSequence('\x1b[' + buf + 'n')
 
1015
 
 
1016
        def m(self, proto, handler, buf):
 
1017
            if not buf:
 
1018
                handler.selectGraphicRendition(NORMAL)
 
1019
            else:
 
1020
                attrs = []
 
1021
                for a in buf.split(';'):
 
1022
                    try:
 
1023
                        a = int(a)
 
1024
                    except ValueError:
 
1025
                        pass
 
1026
                    attrs.append(a)
 
1027
                handler.selectGraphicRendition(*attrs)
 
1028
 
 
1029
    controlSequenceParser = ControlSequenceParser()
 
1030
 
 
1031
    def _handleHeightWidth(self, b):
 
1032
        if b == '3':
 
1033
            self.terminal.doubleHeightLine(True)
 
1034
        elif b == '4':
 
1035
            self.terminal.doubleHeightLine(False)
 
1036
        elif b == '5':
 
1037
            self.terminal.singleWidthLine()
 
1038
        elif b == '6':
 
1039
            self.terminal.doubleWidthLine()
 
1040
        else:
 
1041
            self.terminal.unhandledControlSequence('\x1b#' + b)
 
1042
 
 
1043
 
 
1044
__all__ = [
 
1045
    # Interfaces
 
1046
    'ITerminalProtocol', 'ITerminalTransport',
 
1047
 
 
1048
    # Symbolic constants
 
1049
    'modes', 'privateModes', 'FUNCTION_KEYS',
 
1050
 
 
1051
    'CS_US', 'CS_UK', 'CS_DRAWING', 'CS_ALTERNATE', 'CS_ALTERNATE_SPECIAL',
 
1052
    'G0', 'G1', 'G2', 'G3',
 
1053
 
 
1054
    'UNDERLINE', 'REVERSE_VIDEO', 'BLINK', 'BOLD', 'NORMAL',
 
1055
 
 
1056
    # Protocol classes
 
1057
    'ServerProtocol', 'ClientProtocol']