]> gitweb.factorcode.org Git - factor.git/commitdiff
Merge branch 'master' of git://factorforge.org/git/william42
authorSlava Pestov <slava@slava-pestovs-macbook-pro.local>
Wed, 16 Jul 2008 23:37:01 +0000 (18:37 -0500)
committerSlava Pestov <slava@slava-pestovs-macbook-pro.local>
Wed, 16 Jul 2008 23:37:01 +0000 (18:37 -0500)
extra/backtrack/backtrack.factor
extra/benchmark/backtrack/backtrack.factor [changed mode: 0644->0755]
extra/irc/client/client-tests.factor
extra/irc/client/client.factor
extra/irc/messages/messages-tests.factor [new file with mode: 0644]
extra/irc/messages/messages.factor
extra/irc/ui/load/load.factor
extra/irc/ui/ui.factor

index 7ab11abd6dc508ab6348556b3ca1e6201a10c208..3c1a79412118bd5891a2fab0b58da82e0c3e2b0a 100755 (executable)
@@ -1,20 +1,68 @@
 ! Copyright (C) 2008 William Schlieper\r
 ! See http://factorcode.org/license.txt for BSD license.\r
 \r
-USING: kernel continuations sequences namespaces fry ;\r
+USING: kernel continuations combinators sequences quotations arrays namespaces\r
+       fry summary assocs math math.order macros ;\r
 \r
 IN: backtrack\r
 \r
 SYMBOL: failure\r
 \r
-: amb ( seq -- elt )\r
-    failure get\r
-    '[ , _ '[ , '[ failure set , , continue-with ] callcc0 ] each\r
-       , continue ] callcc1 ;\r
+ERROR: amb-failure ;\r
+\r
+M: amb-failure summary drop "Backtracking failure" ;\r
 \r
 : fail ( -- )\r
-    f amb drop ;\r
+    failure get [ continue ]\r
+    [ amb-failure ] if* ;\r
 \r
 : require ( ? -- )\r
     [ fail ] unless ;\r
 \r
+MACRO: checkpoint ( quot -- quot' )\r
+    '[ failure get ,\r
+       '[ '[ failure set , continue ] callcc0\r
+          , failure set @ ] callcc0 ] ;\r
+\r
+: number-from ( from -- from+n )\r
+    [ 1 + number-from ] checkpoint ;\r
+\r
+<PRIVATE\r
+\r
+: unsafe-number-from-to ( to from -- to from+n )\r
+    2dup = [ [ 1 + unsafe-number-from-to ] checkpoint ] unless ;\r
+\r
+: number-from-to ( to from -- to from+n )\r
+    2dup < [ fail ] when unsafe-number-from-to ;\r
+\r
+: amb-integer ( seq -- int )\r
+    length 1 - 0 number-from-to nip ;\r
+\r
+MACRO: unsafe-amb ( seq -- quot )\r
+    dup length 1 =\r
+    [ first 1quotation ]\r
+    [ [ first ] [ rest ] bi\r
+      '[ , [ drop , unsafe-amb ] checkpoint ] ] if ;\r
+\r
+PRIVATE> \r
+\r
+: amb-lazy ( seq -- elt )\r
+    [ amb-integer ] [ nth ] bi ;\r
+\r
+: amb ( seq -- elt )\r
+    dup empty?\r
+    [ drop fail f ]\r
+    [ unsafe-amb ] if ; inline\r
+\r
+MACRO: amb-execute ( seq -- quot )\r
+    [ length 1 - ] [ <enum> [ 1quotation ] assoc-map ] bi\r
+    '[ , 0 unsafe-number-from-to nip , case ] ;\r
+\r
+: if-amb ( true false -- )\r
+    [\r
+        [ { t f } amb ]\r
+        [ '[ @ require t ] ]\r
+        [ '[ @ f ] ]\r
+        tri* if\r
+    ] with-scope ; inline\r
+\r
old mode 100644 (file)
new mode 100755 (executable)
index 0ffaaa4..df67872
@@ -12,18 +12,6 @@ IN: benchmark.backtrack
 
 : nop ;
 
-MACRO: amb-execute ( seq -- quot )
-    [ length ] [ <enum> [ 1quotation ] assoc-map ] bi
-    '[ , amb , case ] ;
-
-: if-amb ( true false -- )
-    [
-        [ { t f } amb ]
-        [ '[ @ require t ] ]
-        [ '[ @ f ] ]
-        tri* if
-    ] with-scope ; inline
-
 : do-something ( a b -- c )
     { + - * } amb-execute ;
 
index 2883e47b81f15f3fe4278f705d10e3232b2adb8a..100724ea58a18c666503885d12fa824dd8342f47 100644 (file)
@@ -1,7 +1,7 @@
 USING: kernel tools.test accessors arrays sequences qualified
        io.streams.string io.streams.duplex namespaces threads
        calendar irc.client.private irc.client irc.messages.private
-       concurrency.mailboxes classes ;
+       concurrency.mailboxes classes assocs ;
 EXCLUDE: irc.messages => join ;
 RENAME: join irc.messages => join_
 IN: irc.client.tests
@@ -20,28 +20,6 @@ IN: irc.client.tests
 : with-dummy-client ( quot -- )
      rot with-variable ; inline
 
-! Parsing tests
-irc-message new
-    ":someuser!n=user@some.where PRIVMSG #factortest :hi" >>line
-    "someuser!n=user@some.where" >>prefix
-                       "PRIVMSG" >>command
-               { "#factortest" } >>parameters
-                            "hi" >>trailing
-1array
-[ ":someuser!n=user@some.where PRIVMSG #factortest :hi"
-  string>irc-message f >>timestamp ] unit-test
-
-privmsg new
-    ":someuser!n=user@some.where PRIVMSG #factortest :hi" >>line
-    "someuser!n=user@some.where" >>prefix
-                       "PRIVMSG" >>command
-               { "#factortest" } >>parameters
-                            "hi" >>trailing
-                   "#factortest" >>name
-1array
-[ ":someuser!n=user@some.where PRIVMSG #factortest :hi"
-  parse-irc-line f >>timestamp ] unit-test
-
 { "" } make-client dup "factorbot" set-nick current-irc-client [
     { t } [ irc> profile>> nickname>> me? ] unit-test
 
@@ -64,21 +42,29 @@ privmsg new
                     ":some.where 001 factorbot :Welcome factorbot"
                   } make-client
                   [ connect-irc ] keep 1 seconds sleep
-                    profile>> nickname>> ] unit-test
+                  profile>> nickname>> ] unit-test
 
 { join_ "#factortest" } [
-             { ":factorbot!n=factorbo@some.where JOIN :#factortest"
+           { ":factorbot!n=factorbo@some.where JOIN :#factortest"
              ":ircserver.net MODE #factortest +ns"
              ":ircserver.net 353 factorbot @ #factortest :@factorbot "
              ":ircserver.net 366 factorbot #factortest :End of /NAMES list."
              ":ircserver.net 477 factorbot #factortest :[ircserver-info] blah blah"
              } make-client dup "factorbot" set-nick
              [ connect-irc ] keep 1 seconds sleep
-             join-messages>> 5 seconds mailbox-get-timeout
+             join-messages>> 1 seconds mailbox-get-timeout
              [ class ] [ trailing>> ] bi ] unit-test
-! TODO: user join
-! ":somedude!n=user@isp.net JOIN :#factortest"
+
+{ +join+ "somebody" } [
+           { ":somebody!n=somebody@some.where JOIN :#factortest"
+             } make-client dup "factorbot" set-nick
+             [ listeners>> [ "#factortest" [ <irc-channel-listener> ] keep ] dip set-at ]
+             [ connect-irc ]
+             [ listeners>> [ "#factortest" ] dip at
+               [ read-message drop ] [ read-message drop ] [ read-message ] tri ] tri
+             [ action>> ] [ nick>> ] bi
+             ] unit-test
 ! TODO: channel message
-! ":somedude!n=user@isp.net PRIVMSG #factortest :hello"
+! ":somebody!n=somebody@some.where PRIVMSG #factortest :hello"
 ! TODO: direct private message
 ! ":somedude!n=user@isp.net PRIVMSG factorbot2 :hello"
\ No newline at end of file
index 2dbbe8b8f5945b60094219237c26414d17af92e5..405d8ed9ed50fd23d965726daf88e9b047e2773e 100644 (file)
@@ -31,6 +31,20 @@ TUPLE: irc-channel-listener < irc-listener name password timeout participants ;
 TUPLE: irc-nick-listener < irc-listener name ;
 SYMBOL: +server-listener+
 
+! participant modes
+SYMBOL: +operator+
+SYMBOL: +voice+
+SYMBOL: +normal+
+
+: participant-mode ( n -- mode )
+    H{ { 64 +operator+ } { 43 +voice+ } { 0 +normal+ } } at ;
+
+! participant changed actions
+SYMBOL: +join+
+SYMBOL: +part+
+SYMBOL: +mode+
+
+! listener objects
 : <irc-listener> ( -- irc-listener ) <mailbox> <mailbox> irc-listener boa ;
 
 : <irc-server-listener> ( -- irc-server-listener )
@@ -46,6 +60,9 @@ SYMBOL: +server-listener+
 ! Message objects
 ! ======================================
 
+TUPLE: participant-changed nick action ;
+C: <participant-changed> participant-changed
+
 SINGLETON: irc-end          ! sent when the client isn't running anymore
 SINGLETON: irc-disconnected ! sent when connection is lost
 SINGLETON: irc-connected    ! sent when connection is established
@@ -70,19 +87,27 @@ UNION: irc-broadcasted-message irc-end irc-disconnected irc-connected ;
 : listener> ( name -- listener/f ) irc> listeners>> at ;
 : unregister-listener ( name -- ) irc> listeners>> delete-at ;
 
-: to-listener ( message name -- )
+GENERIC: to-listener ( message obj -- )
+
+M: string to-listener ( message string -- )
     listener> [ +server-listener+ listener> ] unless*
-    [ in-messages>> mailbox-put ] [ drop ] if* ;
+    [ to-listener ] [ drop ] if* ;
+
+M: irc-listener to-listener ( message irc-listener -- )
+    in-messages>> mailbox-put ;
 
 : remove-participant ( nick channel -- )
     listener> [ participants>> delete-at ] [ drop ] if* ;
 
+: listeners-with-participant ( nick -- seq )
+    irc> listeners>> values
+    [ dup irc-channel-listener? [ participants>> key? ] [ 2drop f ] if ]
+    with filter ;
+
 : remove-participant-from-all ( nick -- )
-    irc> listeners>>
-    [ irc-channel-listener? [ swap remove-participant ] [ 2drop ] if ] with
-    assoc-each ;
+    dup listeners-with-participant [ delete-at ] with each ;
 
-: add-participant ( nick mode channel -- )
+: add-participant ( mode nick channel -- )
     listener> [ participants>> set-at ] [ 2drop ] if* ;
 
 DEFER: me?
@@ -142,12 +167,31 @@ DEFER: me?
     dup name>> me? [ prefix>> parse-name ] [ name>> ] if ;
 
 : broadcast-message-to-listeners ( message -- )
-    irc> listeners>> values [ in-messages>> mailbox-put ] with each ;
+    irc> listeners>> values [ to-listener ] with each ;
+
+GENERIC: handle-participant-change ( irc-message -- )
+
+M: join handle-participant-change ( join -- )
+    [ prefix>> parse-name +join+ <participant-changed> ]
+    [ trailing>> ] bi to-listener ;
+
+M: part handle-participant-change ( part -- )
+    [ prefix>> parse-name +part+ <participant-changed> ]
+    [ channel>> ] bi to-listener ;
+
+M: kick handle-participant-change ( kick -- )
+    [ who>> +part+ <participant-changed> ]
+    [ channel>> ] bi to-listener ;
+
+M: quit handle-participant-change ( quit -- )
+    prefix>> parse-name
+    [ +part+ <participant-changed> ] [ listeners-with-participant ] bi
+    [ to-listener ] with each ;
 
 GENERIC: handle-incoming-irc ( irc-message -- )
 
 M: irc-message handle-incoming-irc ( irc-message -- )
-    +server-listener+ listener> [ in-messages>> mailbox-put ] [ drop ] if* ;
+    +server-listener+ listener> [ to-listener ] [ drop ] if* ;
 
 M: logged-in handle-incoming-irc ( logged-in -- )
     name>> irc> profile>> (>>nickname) ;
@@ -162,34 +206,43 @@ M: privmsg handle-incoming-irc ( privmsg -- )
     dup irc-message-origin to-listener ;
 
 M: join handle-incoming-irc ( join -- )
-    [ maybe-forward-join ]
-    [ dup trailing>> to-listener ]
-    [ [ drop f ] [ prefix>> parse-name ] [ trailing>> ] tri add-participant ]
-    tri ;
+    { [ maybe-forward-join ] ! keep
+      [ dup trailing>> to-listener ]
+      [ [ drop f ] [ prefix>> parse-name ] [ trailing>> ] tri add-participant ]
+      [ handle-participant-change ]
+    } cleave ;
 
 M: part handle-incoming-irc ( part -- )
-    [ dup channel>> to-listener ] keep
-    [ prefix>> parse-name ] [ channel>> ] bi remove-participant ;
+    [ dup channel>> to-listener ]
+    [ [ prefix>> parse-name ] [ channel>> ] bi remove-participant ]
+    [ handle-participant-change ]
+    tri ;
 
 M: kick handle-incoming-irc ( kick -- )
-    [ dup channel>>  to-listener ]
-    [ [ who>> ] [ channel>> ] bi remove-participant ] 
-    [ dup who>> me? [ unregister-listener ] [ drop ] if ]
-    tri ;
+    { [ dup channel>>  to-listener ]
+      [ [ who>> ] [ channel>> ] bi remove-participant ]
+      [ handle-participant-change ]
+      [ dup who>> me? [ unregister-listener ] [ drop ] if ]
+    } cleave ;
 
 M: quit handle-incoming-irc ( quit -- )
-    [ prefix>> parse-name remove-participant-from-all ] keep
-    call-next-method ;
+    { [ dup prefix>> parse-name listeners-with-participant
+        [ to-listener ] with each ]
+      [ handle-participant-change ]
+      [ prefix>> parse-name remove-participant-from-all ]
+      [ ]
+    } cleave call-next-method ;
 
 : >nick/mode ( string -- nick mode )
-    dup first "+@" member? [ unclip ] [ f ] if ;
+    dup first "+@" member? [ unclip ] [ 0 ] if participant-mode ;
 
 : names-reply>participants ( names-reply -- participants )
     trailing>> [ blank? ] trim " " split
     [ >nick/mode 2array ] map >hashtable ;
 
 M: names-reply handle-incoming-irc ( names-reply -- )
-    [ names-reply>participants ] [ channel>> listener> ] bi (>>participants) ;
+    [ names-reply>participants ] [ channel>> listener> ] bi
+    [ (>>participants) ] [ drop ] if* ;
 
 M: irc-broadcasted-message handle-incoming-irc ( irc-broadcasted-message -- )
     broadcast-message-to-listeners ;
@@ -200,8 +253,8 @@ M: irc-broadcasted-message handle-incoming-irc ( irc-broadcasted-message -- )
 
 GENERIC: handle-outgoing-irc ( obj -- )
 
-M: irc-message handle-outgoing-irc ( irc-message -- )
-!    irc-message>string irc-print ;
+M: irc-message handle-outgoing-irc ( irc-message -- )
+    irc-message>client-line irc-print ;
 
 M: privmsg handle-outgoing-irc ( privmsg -- )
     [ name>> ] [ trailing>> ] bi /PRIVMSG ;
@@ -213,11 +266,6 @@ M: part handle-outgoing-irc ( part -- )
 ! Reader/Writer
 ! ======================================
 
-: irc-mailbox-get ( mailbox quot -- )
-    [ 5 seconds ] dip
-    '[ , , ,  [ mailbox-get-timeout ] dip call ]
-    [ drop ] recover ; inline
-
 : handle-reader-message ( irc-message -- )
     irc> in-messages>> mailbox-put ;
 
@@ -225,7 +273,7 @@ DEFER: (connect-irc)
 
 : (handle-disconnect) ( -- )
     irc>
-        [ [ irc-disconnected ] dip in-messages>> mailbox-put ]
+        [ [ irc-disconnected ] dip to-listener ]
         [ dup reconnect-time>> sleep (connect-irc) ]
         [ profile>> nickname>> /LOGIN ]
     tri ;
@@ -247,14 +295,14 @@ DEFER: (connect-irc)
     [ (reader-loop) ] [ handle-disconnect ] recover ;
 
 : writer-loop ( -- )
-    irc> out-messages>> [ handle-outgoing-irc ] irc-mailbox-get ;
+    irc> out-messages>> mailbox-get handle-outgoing-irc ;
 
 ! ======================================
 ! Processing loops
 ! ======================================
 
 : in-multiplexer-loop ( -- )
-    irc> in-messages>> [ handle-incoming-irc ] irc-mailbox-get ;
+    irc> in-messages>> mailbox-get handle-incoming-irc ;
 
 : strings>privmsg ( name string -- privmsg )
     privmsg new [ (>>trailing) ] keep [ (>>name) ] keep ;
@@ -267,9 +315,8 @@ DEFER: (connect-irc)
     } cond ;
 
 : listener-loop ( name listener -- )
-    out-messages>> swap
-    '[ , swap maybe-annotate-with-name irc> out-messages>> mailbox-put ]
-    irc-mailbox-get ;
+    out-messages>> mailbox-get maybe-annotate-with-name
+    irc> out-messages>> mailbox-put ;
 
 : spawn-irc-loop ( quot name -- )
     [ '[ irc> is-running>> [ @ ] when irc> is-running>> ] ] dip
diff --git a/extra/irc/messages/messages-tests.factor b/extra/irc/messages/messages-tests.factor
new file mode 100644 (file)
index 0000000..1bd6088
--- /dev/null
@@ -0,0 +1,37 @@
+USING: kernel tools.test accessors arrays qualified
+       irc.messages irc.messages.private ;
+EXCLUDE: sequences => join ;
+IN: irc.messages.tests
+
+! Parsing tests
+irc-message new
+    ":someuser!n=user@some.where PRIVMSG #factortest :hi" >>line
+    "someuser!n=user@some.where" >>prefix
+                       "PRIVMSG" >>command
+               { "#factortest" } >>parameters
+                            "hi" >>trailing
+1array
+[ ":someuser!n=user@some.where PRIVMSG #factortest :hi"
+  string>irc-message f >>timestamp ] unit-test
+
+privmsg new
+    ":someuser!n=user@some.where PRIVMSG #factortest :hi" >>line
+    "someuser!n=user@some.where" >>prefix
+                       "PRIVMSG" >>command
+               { "#factortest" } >>parameters
+                            "hi" >>trailing
+                   "#factortest" >>name
+1array
+[ ":someuser!n=user@some.where PRIVMSG #factortest :hi"
+  parse-irc-line f >>timestamp ] unit-test
+
+join new
+    ":someuser!n=user@some.where JOIN :#factortest" >>line
+    "someuser!n=user@some.where" >>prefix
+                          "JOIN" >>command
+                             { } >>parameters
+                   "#factortest" >>trailing
+1array
+[ ":someuser!n=user@some.where JOIN :#factortest"
+  parse-irc-line f >>timestamp ] unit-test
+
index 205630d7903f9d4f1005a085ba98d17e62751409..5813c7272344c030bec71ebac9587037312a97e8 100644 (file)
@@ -21,6 +21,10 @@ TUPLE: mode < irc-message name channel mode ;
 TUPLE: names-reply < irc-message who = channel ;
 TUPLE: unhandled < irc-message ;
 
+: <irc-client-message> ( command parameters trailing -- irc-message )
+    irc-message new now >>timestamp
+    [ [ (>>trailing) ] [ (>>parameters) ] [ (>>command) ] tri ] keep ;
+
 GENERIC: irc-message>client-line ( irc-message -- string )
 
 M: irc-message irc-message>client-line ( irc-message -- string )
@@ -30,6 +34,7 @@ M: irc-message irc-message>client-line ( irc-message -- string )
     tri 3array " " sjoin ;
 
 GENERIC: irc-message>server-line ( irc-message -- string )
+
 M: irc-message irc-message>server-line ( irc-message -- string )
    drop "not implemented yet" ;
 
@@ -58,6 +63,8 @@ M: irc-message irc-message>server-line ( irc-message -- string )
 : split-trailing ( string -- string string/f )
     ":" split1 ;
 
+PRIVATE>
+
 : string>irc-message ( string -- object )
     dup split-prefix split-trailing
     [ [ blank? ] trim " " split unclip swap ] dip
@@ -82,4 +89,3 @@ M: irc-message irc-message>server-line ( irc-message -- string )
     [ [ tuple-slots ] [ parameters>> ] bi append ] dip
     [ all-slots over [ length ] bi@ min head ] keep slots>tuple ;
 
-PRIVATE>
index 6655f310e7f270e3b3e5888a590dacf3760ccc9d..e6f4d07b56492e25bbb8a449cc734fd701d2620e 100755 (executable)
@@ -5,7 +5,7 @@ USING: kernel io.files parser editors sequences ;
 \r
 IN: irc.ui.load\r
 \r
-: file-or ( path path -- path ) over exists? ? ;\r
+: file-or ( path path -- path ) [ [ exists? ] keep ] dip ? ;\r
 \r
 : personal-ui-rc ( -- path ) home ".ircui-rc" append-path ;\r
 \r
index 12f9d0118391b33ab19b03027f39af680f51ea83..a79920efe5698b6f6db99f7f787ff8bb1e87d6f4 100755 (executable)
@@ -5,8 +5,8 @@ USING: accessors kernel threads combinators concurrency.mailboxes
        sequences strings hashtables splitting fry assocs hashtables\r
        ui ui.gadgets ui.gadgets.panes ui.gadgets.editors\r
        ui.gadgets.scrollers ui.commands ui.gadgets.frames ui.gestures\r
-       ui.gadgets.tabs ui.gadgets.grids\r
-       io io.styles namespaces calendar calendar.format\r
+       ui.gadgets.tabs ui.gadgets.grids ui.gadgets.lists ui.gadgets.labels\r
+       io io.styles namespaces calendar calendar.format models\r
        irc.client irc.client.private irc.messages irc.messages.private\r
        irc.ui.commandparser irc.ui.load ;\r
 \r
@@ -18,11 +18,18 @@ SYMBOL: client
 \r
 TUPLE: ui-window client tabs ;\r
 \r
+TUPLE: irc-tab < frame listener client listmodel ;\r
+\r
 : write-color ( str color -- )\r
     foreground associate format ;\r
 : red { 0.5 0 0 1 } ;\r
 : green { 0 0.5 0 1 } ;\r
 : blue { 0 0 1 1 } ;\r
+: black { 0 0 0 1 } ;\r
+\r
+: colors H{ { +operator+ { 0 0.5 0 1 } }\r
+            { +voice+ { 0 0 1 1 } }\r
+            { +normal+ { 0 0 0 1 } } } ;\r
 \r
 : dot-or-parens ( string -- string )\r
     dup empty? [ drop "." ]\r
@@ -64,6 +71,14 @@ M: quit write-irc
     " has left IRC" red write-color\r
     trailing>> dot-or-parens red write-color ;\r
 \r
+M: mode write-irc\r
+    "* " blue write-color\r
+    [ name>> write ] keep\r
+    " has applied mode " blue write-color\r
+    [ mode>> write ] keep\r
+    " to " blue write-color\r
+    channel>> write ;\r
+\r
 M: irc-end write-irc\r
     drop "* You have left IRC" red write-color ;\r
 \r
@@ -84,20 +99,39 @@ M: irc-message write-irc
     [ print-irc ]\r
     [ listener get write-message ] bi ;\r
 \r
-: display ( stream listener -- )\r
+GENERIC: handle-inbox ( tab message -- )\r
+\r
+: filter-participants ( assoc val -- alist )\r
+    [ >alist ] dip\r
+   '[ second , = ] filter ;\r
+\r
+: update-participants ( tab -- )\r
+    [ listmodel>> ] [ listener>> participants>> ] bi\r
+    [ +operator+ filter-participants ]\r
+    [ +voice+ filter-participants ]\r
+    [ +normal+ filter-participants ] tri\r
+    append append swap set-model ;\r
+\r
+M: participant-changed handle-inbox\r
+    drop update-participants ;\r
+\r
+M: object handle-inbox\r
+    nip print-irc ;\r
+\r
+: display ( stream tab -- )\r
     '[ , [ [ t ]\r
-           [ , read-message print-irc ]\r
+           [ , dup listener>> read-message handle-inbox ]\r
            [  ] while ] with-output-stream ] "ircv" spawn drop ;\r
 \r
-: <irc-pane> ( listener -- pane )\r
+: <irc-pane> ( tab -- tab pane )\r
     <scrolling-pane>\r
-    [ <pane-stream> swap display ] keep ;\r
+    [ <pane-stream> swap display ] 2keep ;\r
 \r
 TUPLE: irc-editor < editor outstream listener client ;\r
 \r
-: <irc-editor> ( page pane listener -- client editor )\r
-    irc-editor new-editor\r
-    swap >>listener swap <pane-stream> >>outstream\r
+: <irc-editor> ( tab pane -- tab editor )\r
+    over irc-editor new-editor\r
+    swap listener>> >>listener swap <pane-stream> >>outstream\r
     over client>> >>client ;\r
 \r
 : editor-send ( irc-editor -- )\r
@@ -113,25 +147,36 @@ irc-editor "general" f {
     { T{ key-down f f "ENTER" } editor-send }\r
 } define-command-map\r
 \r
-TUPLE: irc-page < frame listener client ;\r
+: <irc-list> ( -- gadget model )\r
+    [ drop ]\r
+    [ first2 [ <label> ] dip >>color ]\r
+    { } <model> [ <list> ] keep ;\r
+\r
+: <irc-tab> ( listener client -- irc-tab )\r
+    irc-tab new-frame\r
+    swap client>> >>client swap >>listener\r
+    <irc-pane> [ <scroller> @center grid-add* ] keep\r
+    <irc-editor> <scroller> @bottom grid-add* ;\r
+\r
+: <irc-channel-tab> ( listener client -- irc-tab )\r
+    <irc-tab>\r
+    <irc-list> [ <scroller> @right grid-add* ] dip >>listmodel\r
+    [ update-participants ] keep ;\r
 \r
-: <irc-page> ( listener client -- irc-page )\r
-    irc-page new-frame\r
-    swap client>> >>client swap [ >>listener ] keep\r
-    [ <irc-pane> [ <scroller> @center grid-add* ] keep ]\r
-    [ <irc-editor> <scroller> @bottom grid-add* ] bi ;\r
+: <irc-server-tab> ( listener client -- irc-tab )\r
+    <irc-tab> ;\r
 \r
-M: irc-page graft*\r
+M: irc-tab graft*\r
     [ listener>> ] [ client>> ] bi\r
     add-listener ;\r
 \r
-M: irc-page ungraft*\r
+M: irc-tab ungraft*\r
     [ listener>> ] [ client>> ] bi\r
     remove-listener ;\r
 \r
 : join-channel ( name ui-window -- )\r
     [ dup <irc-channel-listener> ] dip\r
-    [ <irc-page> swap ] keep\r
+    [ <irc-channel-tab> swap ] keep\r
     tabs>> add-page ;\r
 \r
 : irc-window ( ui-window -- )\r
@@ -142,12 +187,12 @@ M: irc-page ungraft*
 : ui-connect ( profile -- ui-window )\r
     <irc-client> ui-window new over >>client swap\r
     [ connect-irc ]\r
-    [ listeners>> +server-listener+ swap at <irc-pane> <scroller>\r
+    [ listeners>> +server-listener+ swap at over <irc-tab>\r
       "Server" associate <tabbed> >>tabs ] bi ;\r
 \r
 : server-open ( server port nick password channels -- )\r
     [ <irc-profile> ui-connect [ irc-window ] keep ] dip\r
-    [ over join-channel ] each ;\r
+    [ over join-channel ] each drop ;\r
 \r
 : main-run ( -- ) run-ircui ;\r
 \r