~hudson-openstack/nova/trunk

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/conch/recvline.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.conch.test.test_recvline -*-
 
2
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
 
3
# See LICENSE for details.
 
4
 
 
5
"""
 
6
Basic line editing support.
 
7
 
 
8
@author: Jp Calderone
 
9
"""
 
10
 
 
11
import string
 
12
 
 
13
from zope.interface import implements
 
14
 
 
15
from twisted.conch.insults import insults, helper
 
16
 
 
17
from twisted.python import log, reflect
 
18
 
 
19
_counters = {}
 
20
class Logging(object):
 
21
    """Wrapper which logs attribute lookups.
 
22
 
 
23
    This was useful in debugging something, I guess.  I forget what.
 
24
    It can probably be deleted or moved somewhere more appropriate.
 
25
    Nothing special going on here, really.
 
26
    """
 
27
    def __init__(self, original):
 
28
        self.original = original
 
29
        key = reflect.qual(original.__class__)
 
30
        count = _counters.get(key, 0)
 
31
        _counters[key] = count + 1
 
32
        self._logFile = file(key + '-' + str(count), 'w')
 
33
 
 
34
    def __str__(self):
 
35
        return str(super(Logging, self).__getattribute__('original'))
 
36
 
 
37
    def __repr__(self):
 
38
        return repr(super(Logging, self).__getattribute__('original'))
 
39
 
 
40
    def __getattribute__(self, name):
 
41
        original = super(Logging, self).__getattribute__('original')
 
42
        logFile = super(Logging, self).__getattribute__('_logFile')
 
43
        logFile.write(name + '\n')
 
44
        return getattr(original, name)
 
45
 
 
46
class TransportSequence(object):
 
47
    """An L{ITerminalTransport} implementation which forwards calls to
 
48
    one or more other L{ITerminalTransport}s.
 
49
 
 
50
    This is a cheap way for servers to keep track of the state they
 
51
    expect the client to see, since all terminal manipulations can be
 
52
    send to the real client and to a terminal emulator that lives in
 
53
    the server process.
 
54
    """
 
55
    implements(insults.ITerminalTransport)
 
56
 
 
57
    for keyID in ('UP_ARROW', 'DOWN_ARROW', 'RIGHT_ARROW', 'LEFT_ARROW',
 
58
                  'HOME', 'INSERT', 'DELETE', 'END', 'PGUP', 'PGDN',
 
59
                  'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9',
 
60
                  'F10', 'F11', 'F12'):
 
61
        exec '%s = object()' % (keyID,)
 
62
 
 
63
    TAB = '\t'
 
64
    BACKSPACE = '\x7f'
 
65
 
 
66
    def __init__(self, *transports):
 
67
        assert transports, "Cannot construct a TransportSequence with no transports"
 
68
        self.transports = transports
 
69
 
 
70
    for method in insults.ITerminalTransport:
 
71
        exec """\
 
72
def %s(self, *a, **kw):
 
73
    for tpt in self.transports:
 
74
        result = tpt.%s(*a, **kw)
 
75
    return result
 
76
""" % (method, method)
 
77
 
 
78
class LocalTerminalBufferMixin(object):
 
79
    """A mixin for RecvLine subclasses which records the state of the terminal.
 
80
 
 
81
    This is accomplished by performing all L{ITerminalTransport} operations on both
 
82
    the transport passed to makeConnection and an instance of helper.TerminalBuffer.
 
83
 
 
84
    @ivar terminalCopy: A L{helper.TerminalBuffer} instance which efforts
 
85
    will be made to keep up to date with the actual terminal
 
86
    associated with this protocol instance.
 
87
    """
 
88
 
 
89
    def makeConnection(self, transport):
 
90
        self.terminalCopy = helper.TerminalBuffer()
 
91
        self.terminalCopy.connectionMade()
 
92
        return super(LocalTerminalBufferMixin, self).makeConnection(
 
93
            TransportSequence(transport, self.terminalCopy))
 
94
 
 
95
    def __str__(self):
 
96
        return str(self.terminalCopy)
 
97
 
 
98
class RecvLine(insults.TerminalProtocol):
 
99
    """L{TerminalProtocol} which adds line editing features.
 
100
 
 
101
    Clients will be prompted for lines of input with all the usual
 
102
    features: character echoing, left and right arrow support for
 
103
    moving the cursor to different areas of the line buffer, backspace
 
104
    and delete for removing characters, and insert for toggling
 
105
    between typeover and insert mode.  Tabs will be expanded to enough
 
106
    spaces to move the cursor to the next tabstop (every four
 
107
    characters by default).  Enter causes the line buffer to be
 
108
    cleared and the line to be passed to the lineReceived() method
 
109
    which, by default, does nothing.  Subclasses are responsible for
 
110
    redrawing the input prompt (this will probably change).
 
111
    """
 
112
    width = 80
 
113
    height = 24
 
114
 
 
115
    TABSTOP = 4
 
116
 
 
117
    ps = ('>>> ', '... ')
 
118
    pn = 0
 
119
 
 
120
    def connectionMade(self):
 
121
        # A list containing the characters making up the current line
 
122
        self.lineBuffer = []
 
123
 
 
124
        # A zero-based (wtf else?) index into self.lineBuffer.
 
125
        # Indicates the current cursor position.
 
126
        self.lineBufferIndex = 0
 
127
 
 
128
        t = self.terminal
 
129
        # A map of keyIDs to bound instance methods.
 
130
        self.keyHandlers = {
 
131
            t.LEFT_ARROW: self.handle_LEFT,
 
132
            t.RIGHT_ARROW: self.handle_RIGHT,
 
133
            t.TAB: self.handle_TAB,
 
134
 
 
135
            # Both of these should not be necessary, but figuring out
 
136
            # which is necessary is a huge hassle.
 
137
            '\r': self.handle_RETURN,
 
138
            '\n': self.handle_RETURN,
 
139
 
 
140
            t.BACKSPACE: self.handle_BACKSPACE,
 
141
            t.DELETE: self.handle_DELETE,
 
142
            t.INSERT: self.handle_INSERT,
 
143
            t.HOME: self.handle_HOME,
 
144
            t.END: self.handle_END}
 
145
 
 
146
        self.initializeScreen()
 
147
 
 
148
    def initializeScreen(self):
 
149
        # Hmm, state sucks.  Oh well.
 
150
        # For now we will just take over the whole terminal.
 
151
        self.terminal.reset()
 
152
        self.terminal.write(self.ps[self.pn])
 
153
        # XXX Note: I would prefer to default to starting in insert
 
154
        # mode, however this does not seem to actually work!  I do not
 
155
        # know why.  This is probably of interest to implementors
 
156
        # subclassing RecvLine.
 
157
 
 
158
        # XXX XXX Note: But the unit tests all expect the initial mode
 
159
        # to be insert right now.  Fuck, there needs to be a way to
 
160
        # query the current mode or something.
 
161
        # self.setTypeoverMode()
 
162
        self.setInsertMode()
 
163
 
 
164
    def currentLineBuffer(self):
 
165
        s = ''.join(self.lineBuffer)
 
166
        return s[:self.lineBufferIndex], s[self.lineBufferIndex:]
 
167
 
 
168
    def setInsertMode(self):
 
169
        self.mode = 'insert'
 
170
        self.terminal.setModes([insults.modes.IRM])
 
171
 
 
172
    def setTypeoverMode(self):
 
173
        self.mode = 'typeover'
 
174
        self.terminal.resetModes([insults.modes.IRM])
 
175
 
 
176
    def drawInputLine(self):
 
177
        """
 
178
        Write a line containing the current input prompt and the current line
 
179
        buffer at the current cursor position.
 
180
        """
 
181
        self.terminal.write(self.ps[self.pn] + ''.join(self.lineBuffer))
 
182
 
 
183
    def terminalSize(self, width, height):
 
184
        # XXX - Clear the previous input line, redraw it at the new
 
185
        # cursor position
 
186
        self.terminal.eraseDisplay()
 
187
        self.terminal.cursorHome()
 
188
        self.width = width
 
189
        self.height = height
 
190
        self.drawInputLine()
 
191
 
 
192
    def unhandledControlSequence(self, seq):
 
193
        pass
 
194
 
 
195
    def keystrokeReceived(self, keyID, modifier):
 
196
        m = self.keyHandlers.get(keyID)
 
197
        if m is not None:
 
198
            m()
 
199
        elif keyID in string.printable:
 
200
            self.characterReceived(keyID, False)
 
201
        else:
 
202
            log.msg("Received unhandled keyID: %r" % (keyID,))
 
203
 
 
204
    def characterReceived(self, ch, moreCharactersComing):
 
205
        if self.mode == 'insert':
 
206
            self.lineBuffer.insert(self.lineBufferIndex, ch)
 
207
        else:
 
208
            self.lineBuffer[self.lineBufferIndex:self.lineBufferIndex+1] = [ch]
 
209
        self.lineBufferIndex += 1
 
210
        self.terminal.write(ch)
 
211
 
 
212
    def handle_TAB(self):
 
213
        n = self.TABSTOP - (len(self.lineBuffer) % self.TABSTOP)
 
214
        self.terminal.cursorForward(n)
 
215
        self.lineBufferIndex += n
 
216
        self.lineBuffer.extend(' ' * n)
 
217
 
 
218
    def handle_LEFT(self):
 
219
        if self.lineBufferIndex > 0:
 
220
            self.lineBufferIndex -= 1
 
221
            self.terminal.cursorBackward()
 
222
 
 
223
    def handle_RIGHT(self):
 
224
        if self.lineBufferIndex < len(self.lineBuffer):
 
225
            self.lineBufferIndex += 1
 
226
            self.terminal.cursorForward()
 
227
 
 
228
    def handle_HOME(self):
 
229
        if self.lineBufferIndex:
 
230
            self.terminal.cursorBackward(self.lineBufferIndex)
 
231
            self.lineBufferIndex = 0
 
232
 
 
233
    def handle_END(self):
 
234
        offset = len(self.lineBuffer) - self.lineBufferIndex
 
235
        if offset:
 
236
            self.terminal.cursorForward(offset)
 
237
            self.lineBufferIndex = len(self.lineBuffer)
 
238
 
 
239
    def handle_BACKSPACE(self):
 
240
        if self.lineBufferIndex > 0:
 
241
            self.lineBufferIndex -= 1
 
242
            del self.lineBuffer[self.lineBufferIndex]
 
243
            self.terminal.cursorBackward()
 
244
            self.terminal.deleteCharacter()
 
245
 
 
246
    def handle_DELETE(self):
 
247
        if self.lineBufferIndex < len(self.lineBuffer):
 
248
            del self.lineBuffer[self.lineBufferIndex]
 
249
            self.terminal.deleteCharacter()
 
250
 
 
251
    def handle_RETURN(self):
 
252
        line = ''.join(self.lineBuffer)
 
253
        self.lineBuffer = []
 
254
        self.lineBufferIndex = 0
 
255
        self.terminal.nextLine()
 
256
        self.lineReceived(line)
 
257
 
 
258
    def handle_INSERT(self):
 
259
        assert self.mode in ('typeover', 'insert')
 
260
        if self.mode == 'typeover':
 
261
            self.setInsertMode()
 
262
        else:
 
263
            self.setTypeoverMode()
 
264
 
 
265
    def lineReceived(self, line):
 
266
        pass
 
267
 
 
268
class HistoricRecvLine(RecvLine):
 
269
    """L{TerminalProtocol} which adds both basic line-editing features and input history.
 
270
 
 
271
    Everything supported by L{RecvLine} is also supported by this class.  In addition, the
 
272
    up and down arrows traverse the input history.  Each received line is automatically
 
273
    added to the end of the input history.
 
274
    """
 
275
    def connectionMade(self):
 
276
        RecvLine.connectionMade(self)
 
277
 
 
278
        self.historyLines = []
 
279
        self.historyPosition = 0
 
280
 
 
281
        t = self.terminal
 
282
        self.keyHandlers.update({t.UP_ARROW: self.handle_UP,
 
283
                                 t.DOWN_ARROW: self.handle_DOWN})
 
284
 
 
285
    def currentHistoryBuffer(self):
 
286
        b = tuple(self.historyLines)
 
287
        return b[:self.historyPosition], b[self.historyPosition:]
 
288
 
 
289
    def _deliverBuffer(self, buf):
 
290
        if buf:
 
291
            for ch in buf[:-1]:
 
292
                self.characterReceived(ch, True)
 
293
            self.characterReceived(buf[-1], False)
 
294
 
 
295
    def handle_UP(self):
 
296
        if self.lineBuffer and self.historyPosition == len(self.historyLines):
 
297
            self.historyLines.append(self.lineBuffer)
 
298
        if self.historyPosition > 0:
 
299
            self.handle_HOME()
 
300
            self.terminal.eraseToLineEnd()
 
301
 
 
302
            self.historyPosition -= 1
 
303
            self.lineBuffer = []
 
304
 
 
305
            self._deliverBuffer(self.historyLines[self.historyPosition])
 
306
 
 
307
    def handle_DOWN(self):
 
308
        if self.historyPosition < len(self.historyLines) - 1:
 
309
            self.handle_HOME()
 
310
            self.terminal.eraseToLineEnd()
 
311
 
 
312
            self.historyPosition += 1
 
313
            self.lineBuffer = []
 
314
 
 
315
            self._deliverBuffer(self.historyLines[self.historyPosition])
 
316
        else:
 
317
            self.handle_HOME()
 
318
            self.terminal.eraseToLineEnd()
 
319
 
 
320
            self.historyPosition = len(self.historyLines)
 
321
            self.lineBuffer = []
 
322
            self.lineBufferIndex = 0
 
323
 
 
324
    def handle_RETURN(self):
 
325
        if self.lineBuffer:
 
326
            self.historyLines.append(''.join(self.lineBuffer))
 
327
        self.historyPosition = len(self.historyLines)
 
328
        return RecvLine.handle_RETURN(self)