def test_Metaline_delete(): fores = RunLengthList([(0, 'F'), (4, 'B'), (7, 'Z')]) backs = RunLengthList([(0, 'F'), (4, 'B'), (7, 'Z')]) m = Metaline('foobarbaz', fores, backs) m.delete(3, 6) assert m.line == 'foobaz', m.line assert m.fores.items() == m.backs.items() == [(0, 'F'), (3, 'B'), (4, 'Z')]
def test_change_fore_with_no_right_bound(self): ml = Metaline("foobarbaz", RunLengthList([(0, fg_code(WHITE, False))]), RunLengthList([(0, None)])) self.la.change_fore(3, None, fg_code(RED, False)) res = self.la.apply(ml) assert res.fores.items() == [(0, fg_code(WHITE, False)), (3, fg_code(RED, False))]
def test_Metaline_insert_adjustsindices_beyond_span(): fores = RunLengthList([(0, 'F'), (5, 'B')]) backs = RunLengthList([(0, 'F'), (5, 'B')]) m = Metaline('foobar', fores, backs) m.insert(3, 'baz') assert m.fores.items() == [(0, 'F'), (8, 'B')], m.fores.items() assert m.backs.items() == [(0, 'F'), (8, 'B')], m.backs.items()
def setUp(self): self.f = TelnetClientFactory(None, 'ascii', None) self.f.realm = self.e = Mock(spec=RootRealm) self.tc = TelnetClient(self.f) self.tc.transport = FakeTransport() self.fores = RunLengthList([(0, fg_code(WHITE, False))]) self.backs = RunLengthList([(0, bg_code(BLACK))])
def test_insert_metaline(self): ml_one = simpleml('foo', 'foo', 'foo') self.la.insert_metaline(1, simpleml('bar', 'bar', 'bar')) res = self.la.apply(ml_one) assert res == Metaline( 'fbaroo', RunLengthList([(0, 'foo'), (1, 'bar'), (4, 'foo')]), RunLengthList([(0, 'foo'), (1, 'bar'), (4, 'foo')]))
def test_insert_metaline(): ml_one = Metaline('foo baz', RunLengthList([(0, 'foo'), (4, 'baz')]), RunLengthList([(0, 'foo'), (4, 'baz')])) ml_two = simpleml('bar ', 'bar', 'bar') ml_one.insert_metaline(4, ml_two) assert ml_one == Metaline( "foo bar baz", RunLengthList([(0, 'foo'), (4, 'bar'), (8, 'baz')]), RunLengthList([(0, 'foo'), (4, 'bar'), (8, 'baz')]))
def test_metalineReceived_sets_fore_and_back_for_overruns(self): colours = [] self.o.change_colour = lambda: colours.append( (self.o.fore, self.o.back)) self.o.metalineReceived( Metaline("foo", RunLengthList([(0, self.f1), (3, self.f2)]), RunLengthList([(0, self.b1), (6, self.b2)]))) assert colours == [(self.f1, self.b1), (self.f2, self.b2)]
def test_metalineReceived_calls_change_colour_for_overrun_colours(self): self.o.metalineReceived( Metaline("foo", RunLengthList([(0, self.f1), (3, self.f2)]), RunLengthList([(0, self.b1), (6, self.b2)]))) print self.o.method_calls assert self.o.method_calls == [("change_colour", (), {}), ("write_out_span", ("foo", ), {}), ("change_colour", (), {})]
def test_as_populated_list_throws_error_if_clearing_start(): r = RunLengthList([(0, 'foo'), (3, 'bar')]) try: r.blank_between(0, 2) except ValueError: pass else: assert False
def test_target_seen_highlighting(self): ml = Metaline('bar foo baz', RunLengthList([(0, 'foo')]), RunLengthList([(0, 'bar')])) ti = TriggerMatchingRealm(ml, self.r, self.r, Mock()) a = ti.alterer self.t.target_seen.func(mobj, ti) res = a.apply(ml) assert res.fores.items() == [(0, 'foo'), (4, fg_code(RED, True)), (7, 'foo')]
def test_metalineReceived_change_colour_mid_span_calls_change_colour(self): self.o.metalineReceived( Metaline("foobarbaz", RunLengthList([(0, self.f1), (3, self.f2)]), RunLengthList([(0, self.b1), (6, self.b2)]))) assert self.o.method_calls == [("change_colour", (), {}), ("write_out_span", ("foo", ), {}), ("change_colour", (), {}), ("write_out_span", ("bar", ), {}), ("change_colour", (), {}), ("write_out_span", ("baz", ), {})]
def test_highlights(self): m = Metaline("foo", RunLengthList([(0, 'foo')]), RunLengthList([(0, 'bar')])) root = RootRealm(Mock(spec=TelnetClientFactory)) realm = TriggerMatchingRealm(m, root, root, Mock()) match = re.search('foobar', 'foobar') self.trig.func(match, realm) res = realm.alterer.apply(m) assert res.fores.items() == \ [(0, HexFGCode(0x80, 0xFF, 0x80)), (6, 'foo')]
def parseline(self, line): """Interpret the VT100 codes in line and returns a Metaline, replete with RunLengthLists, that splits the text, foreground and background into three separate channels. """ fores, backs, cleanline = self._parseline(line) rlfores = RunLengthList(((length, fg_code(colour, bold)) for (length, (colour, bold)) in fores), _normalised=True) rlbacks = RunLengthList( ((length, bg_code(colour)) for (length, colour) in backs), _normalised=True) return Metaline(cleanline, rlfores, rlbacks)
def test_change_fore_with_trailing_colours(self): #don't know why, but this test exposed what looked like a quasi-random #failure... ml = Metaline( 'foobars eggs.', RunLengthList([(0, fg_code(CYAN, False)), (13, fg_code(WHITE, False))]), RunLengthList([(0, None)])) self.la.change_fore(0, 7, fg_code(RED, True)) res = self.la.apply(ml) expected = [(0, fg_code(RED, True)), (7, fg_code(CYAN, False)), (13, fg_code(WHITE, False))] assert res.fores.items() == expected, \ res.fores.items()
def test_lineReceived_parses_colours(self): expected = [ Metaline('foo', RunLengthList([(0, fg_code(RED, False))]), self.backs, wrap=True) ] self.tc.lineReceived('\x1b[31mfoo') lines = [ line for ((line, ), kwargs) in self.e.metalineReceived.call_args_list ] assert lines == expected, lines
def __init__(self, gui): gtk.TextView.__init__(self) #the identity of the return value of get_buffer() doesn't seem to be #stable. before, we used a property, but now we just get it once and #leave it at that because GTK complains about the non-identicality #of them. self.gui = gui self.buffer = self.get_buffer() self.paused = False self.end_mark = self.buffer.create_mark('end_mark', self.buffer.get_end_iter(), False) self.timestamps = RunLengthList({}) self.connect('focus-in-event', self.got_focus_cb) self.set_property("has-tooltip", True) self.connect("query-tooltip", self.display_tooltip_cb) self.set_editable(False) self.set_cursor_visible(False) self.set_wrap_mode(gtk.WRAP_CHAR) self.modify_base(gtk.STATE_NORMAL, gtk.gdk.Color(0, 0, 0)) #sic self.modify_font(pango.FontDescription('terminus 12')) self._tags = {}
def test_get_at_works_right_before_colour_change(): rll = RunLengthList({0: 'foo', 2: 'bar'}) assert rll.get_at(1) == 'foo'
def test_get_at_works_on_colour_change(): rll = RunLengthList({0: 'foo', 2: 'bar'}) assert rll.get_at(2) == 'bar'
def __init__(self, *args): self.adjustments = [] RunLengthList.__init__(self, *args)
def test_Metaline_change_fg_sets_colour(): m = Metaline('foo', RunLengthList([(0, None)]), None) m.change_fore(0, 5, 'foo') assert m.fores.items() == [(0, 'foo'), (5, None)]
def test_equality_simple(): a = Metaline('foo', RunLengthList([(0, 'foo')]), RunLengthList([(0, 'bar')])) b = Metaline('foo', RunLengthList([(0, 'foo')]), RunLengthList([(0, 'bar')])) assert a == b, (a, b)
def setUp(self): self.la = LineAlterer() self.ml = Metaline('spam eggs ham', RunLengthList([(0, 'A'), (2, 'B'), (4, 'C')]), RunLengthList([(0, 'D'), (2, 'E'), (4, 'F')]))
def test_Metaline_insert_fg_bg_on_too_long(): fores, backs = RunLengthList([(0, 'foo')]), RunLengthList([(0, 'bar')]) m = Metaline('foo', fores.copy(), backs.copy()) m.insert(2, 'bar') assert m.fores == fores, m.backs == backs
def index_adjust(self, index, adjustment): RunLengthList.index_adjust(self, index, adjustment) self.adjustments.append((index, adjustment))
def test_changes_fg_normally(self): metaline = Metaline('foobar', RunLengthList([(0, 'FOO')]), None) metaline.change_fore(1, 3, 'BAR') assert metaline.fores.items() == [(0, 'FOO'), (1, 'BAR'), (3, 'FOO')]
def test_preserves_engulfed_colour_starts(self): metaline = Metaline('foobar', RunLengthList([(0, 'FOO'), (2, 'BAR')]), None) metaline.change_fore(1, 3, 'BAZ') assert metaline.fores.items() == [(0, 'FOO'), (1, 'BAZ'), (3, 'BAR')]
def test_change_fore_with_trailing_colours(): ml = Metaline('foo bar', RunLengthList([(0, 'foo'), (7, 'bar')]), RunLengthList([(0, 'bar')])) ml.change_fore(0, 3, 'baz') assert ml.fores.items() == [(0, 'baz'), (3, 'foo'), (7, 'bar')]
class OutputView(gtk.TextView): """The display for all the text received from the MUD.""" def __init__(self, gui): gtk.TextView.__init__(self) #the identity of the return value of get_buffer() doesn't seem to be #stable. before, we used a property, but now we just get it once and #leave it at that because GTK complains about the non-identicality #of them. self.gui = gui self.buffer = self.get_buffer() self.paused = False self.end_mark = self.buffer.create_mark('end_mark', self.buffer.get_end_iter(), False) self.timestamps = RunLengthList({}) self.connect('focus-in-event', self.got_focus_cb) self.set_property("has-tooltip", True) self.connect("query-tooltip", self.display_tooltip_cb) self.set_editable(False) self.set_cursor_visible(False) self.set_wrap_mode(gtk.WRAP_CHAR) self.modify_base(gtk.STATE_NORMAL, gtk.gdk.Color(0, 0, 0)) #sic self.modify_font(pango.FontDescription('terminus 12')) self._tags = {} def got_focus_cb(self, widget, event): """We never want focus; the command line automatically lets us have all incoming keypresses that we're interested in. """ if self.gui: self.gui.command_line.grab_focus() def display_tooltip_cb(self, widget, wx, wy, keyboard_mode, tooltip): """Display a timestamp for the line the user hovers over.""" #XXX: I'm not sure this is converting between coordinates right, I #need to double-check the GTK docs. bx, by = self.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, wx, wy) textiter = self.get_iter_at_location(bx, by) #GTK is very keen for the above code to succeed, but really it's only #useful for us if there's a tooltip above a bit of text, as opposed #to the ENTIRE FREAKING WIDGET. So test to see if bx and by can #roundtrip in the character's pixel rectangle rect = self.get_iter_location(textiter) if not 0 <= bx - rect.x <= rect.width or \ not 0 <= by - rect.y <= rect.height: return False received_at = self.timestamps.get_at(textiter.get_offset()) tooltip.set_text(received_at.strftime("Received at: %H:%M:%S")) return True def pause(self): """Stop autoscrolling to new data.""" if not self.paused: self.paused = True if self.gui: self.gui.paused_label.set_markup("PAUSED") def unpause(self): """Restart autoscrolling to new data. This does not automatically scroll to the buffer's end. """ if self.paused: self.paused = False if self.gui: self.gui.paused_label.set_markup("") #scroll to the end of output self.scroll_mark_onscreen(self.end_mark) def show_metaline(self, metaline): """Write a span of text to the window using the colours defined in the other channels. This will autoscroll to the end if we are not paused. """ bytes = metaline.line.encode('utf-8') end_iter = self.buffer.get_end_iter() offset = end_iter.get_offset() self.buffer.insert(end_iter, bytes) self.apply_colours(metaline.fores, offset, len(metaline.line)) self.apply_colours(metaline.backs, offset, len(metaline.line)) if not self.paused: self.scroll_mark_onscreen(self.end_mark) else: if self.gui: self.gui.paused_label.set_markup("<span foreground='#FFFFFF' " "background='#000000'>" "MORE - PAUSED</span>") #this is a bit naughty, we're bypassing the RLL's safety thingies #anyway, we need to store the offset that -begins- the chunk of text self.timestamps[offset] = datetime.now() def apply_colours(self, colours, offset, end_offset): """Apply a RunLengthList of colours to the buffer, starting at offset characters in. """ end_iter = self.buffer.get_iter_at_offset(offset) for tag, end in zip(map(self.fetch_tag, colours.values()), colours.keys()[1:] + [end_offset]): start_iter = end_iter end_iter = self.buffer.get_iter_at_offset(end + offset) self.buffer.apply_tag(tag, start_iter, end_iter) def fetch_tag(self, colour): """Check to see if a colour is in the tag table. If it isn't, add it. Returns the tag. """ #use our own tag table, because GTK's is too slow if colour in self._tags: tag = self._tags[colour] else: tag = self.buffer.create_tag(None) tag.set_property(colour.ground + 'ground', '#' + colour.as_hex) tag.set_property(colour.ground + 'ground-set', True) self._tags[colour] = tag return tag
def test_Metaline_change_bg_sets_colour(): m = Metaline("foo", None, RunLengthList([(0, None)])) m.change_back(2, 3, 'bar') assert m.backs.items() == [(0, None), (2, 'bar'), (3, None)], \ m.backs.items()
def test_changes_fg_right_at_end(self): metaline = Metaline('foobar', RunLengthList([(0, 'FOO')]), None) metaline.change_fore(1, 6, 'BAR') assert metaline.fores.items() == [(0, 'FOO'), (1, 'BAR'), (6, 'FOO')]