[cmdopts] Chaos
authorMyhailo Danylenko <isbear@ukrpost.net>
Tue, 05 Mar 2013 01:11:24 +0200
changeset 74 a879ea179877
parent 73 feae79fcc408
child 75 17cd00b2e722
[cmdopts] Chaos
cmdopts.diff
--- a/cmdopts.diff	Wed Feb 27 23:35:53 2013 +0200
+++ b/cmdopts.diff	Tue Mar 05 01:11:24 2013 +0200
@@ -1,6 +1,12 @@
 # HG changeset patch
 # Parent 92fa48ef53c909928706ab4c51518953339a38e4
-Unified command option parsing
+[work-in-progress] Unified command option parsing
+
+  * wrecking chaos all over the place
+  * started integration with cmd subsystem
+  * broke everything
+
+  ** PREVIOUS ACHIEVEMENTS **
 
   * cmdopts_parse() & cmdopts_free() in utils.c/h
   * /roster uses parser
@@ -29,7 +35,7 @@
 
 diff -r 92fa48ef53c9 mcabber/doc/help/cs/hlp_buffer.txt
 --- a/mcabber/doc/help/cs/hlp_buffer.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/cs/hlp_buffer.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/cs/hlp_buffer.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -25,7 +25,7 @@
   Přesune se o [n] řádků nahoru (výchozí: polovina obrazovky).
  /buffer down [n]
@@ -41,7 +47,7 @@
   Přesune se na procentuální pozici n%.
 diff -r 92fa48ef53c9 mcabber/doc/help/cs/hlp_del.txt
 --- a/mcabber/doc/help/cs/hlp_del.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/cs/hlp_del.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/cs/hlp_del.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,4 +1,4 @@
  
 - /DEL
@@ -50,7 +56,7 @@
  Smaže aktuální kontakt ze seznamu kontaktů (rosteru) a zruší povolení oznamování o stavu daného kontaktu (autorizaci) na obou stranách.
 diff -r 92fa48ef53c9 mcabber/doc/help/cs/hlp_move.txt
 --- a/mcabber/doc/help/cs/hlp_move.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/cs/hlp_move.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/cs/hlp_move.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,5 +1,6 @@
  
 - /MOVE [skupina]
@@ -61,7 +67,7 @@
  Tip: V módu rozhovoru lze použít "/roster alternate" pro skok na přesunutý kontakt.
 diff -r 92fa48ef53c9 mcabber/doc/help/cs/hlp_rename.txt
 --- a/mcabber/doc/help/cs/hlp_rename.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/cs/hlp_rename.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/cs/hlp_rename.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,4 +1,6 @@
  
 - /RENAME jméno
@@ -73,7 +79,7 @@
 +Optionally you can use one of --jid, --group or --name to select object, different from current.
 diff -r 92fa48ef53c9 mcabber/doc/help/de/hlp_buffer.txt
 --- a/mcabber/doc/help/de/hlp_buffer.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/de/hlp_buffer.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/de/hlp_buffer.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -25,7 +25,7 @@
   Scrollt den Puffer um n Zeilen hoch. Gibt man keine Zahl an, scrollt er um einen halben Bildschirm
  /buffer down [n]
@@ -85,7 +91,7 @@
   Springe zur Position "n" im Chatpuffer
 diff -r 92fa48ef53c9 mcabber/doc/help/de/hlp_del.txt
 --- a/mcabber/doc/help/de/hlp_del.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/de/hlp_del.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/de/hlp_del.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,4 +1,4 @@
  
 - /DEL
@@ -94,7 +100,7 @@
  Löscht den gerade ausgewählten Buddy vom Roster. Außerdem werden die automatischen Presence Benachrichtigungen vom/zum Buddy gestoppt.
 diff -r 92fa48ef53c9 mcabber/doc/help/de/hlp_move.txt
 --- a/mcabber/doc/help/de/hlp_move.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/de/hlp_move.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/de/hlp_move.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,6 +1,7 @@
  
 - /MOVE [groupname]
@@ -106,7 +112,7 @@
  Tipp: Wenn der Chatmodus aktiviert ist, kannst du "/roster alternate" benutzen um zu dem gerade bewegten Buddy zu springen.
 diff -r 92fa48ef53c9 mcabber/doc/help/de/hlp_rename.txt
 --- a/mcabber/doc/help/de/hlp_rename.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/de/hlp_rename.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/de/hlp_rename.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,4 +1,6 @@
  
 - /RENAME name
@@ -118,7 +124,7 @@
 +Optionally you can use one of --jid, --group or --name to select object, different from current.
 diff -r 92fa48ef53c9 mcabber/doc/help/en/hlp_buffer.txt
 --- a/mcabber/doc/help/en/hlp_buffer.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/en/hlp_buffer.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/en/hlp_buffer.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -25,7 +25,7 @@
   Scroll the buffer up [n] lines (default: half a screen)
  /buffer down [n]
@@ -130,7 +136,7 @@
   Jump to position %n of the buddy chat buffer
 diff -r 92fa48ef53c9 mcabber/doc/help/en/hlp_del.txt
 --- a/mcabber/doc/help/en/hlp_del.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/en/hlp_del.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/en/hlp_del.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,4 +1,4 @@
  
 - /DEL
@@ -140,7 +146,7 @@
 +Delete the current buddy or one, specified with [jid] from our roster, unsubscribe from its presence notification and unsubscribe it from ours.
 diff -r 92fa48ef53c9 mcabber/doc/help/en/hlp_move.txt
 --- a/mcabber/doc/help/en/hlp_move.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/en/hlp_move.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/en/hlp_move.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,5 +1,6 @@
  
 - /MOVE [groupname]
@@ -151,7 +157,7 @@
  Tip: if the chatmode is enabled, you can use "/roster alternate" to jump to the moved buddy.
 diff -r 92fa48ef53c9 mcabber/doc/help/en/hlp_rename.txt
 --- a/mcabber/doc/help/en/hlp_rename.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/en/hlp_rename.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/en/hlp_rename.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,4 +1,6 @@
  
 - /RENAME name
@@ -163,7 +169,7 @@
 +Optionally you can use one of --jid, --group or --name to select object, different from current.
 diff -r 92fa48ef53c9 mcabber/doc/help/fr/hlp_buffer.txt
 --- a/mcabber/doc/help/fr/hlp_buffer.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/fr/hlp_buffer.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/fr/hlp_buffer.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -25,7 +25,7 @@
   Défile vers le haut de [n] lignes (par défaut un demi écran)
  /buffer down [n]
@@ -175,7 +181,7 @@
   Va à la position n% du tampon
 diff -r 92fa48ef53c9 mcabber/doc/help/fr/hlp_del.txt
 --- a/mcabber/doc/help/fr/hlp_del.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/fr/hlp_del.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/fr/hlp_del.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,4 +1,4 @@
  
 - /DEL
@@ -184,7 +190,7 @@
  Supprime le contact sélectionné du roster, supprime notre abonnement à ses notifications de présence et supprime son abonnement aux nôtres.
 diff -r 92fa48ef53c9 mcabber/doc/help/fr/hlp_move.txt
 --- a/mcabber/doc/help/fr/hlp_move.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/fr/hlp_move.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/fr/hlp_move.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,5 +1,6 @@
  
 - /MOVE [groupname]
@@ -195,7 +201,7 @@
  Astuce : si le mode discussion (chatmode) est activé, vous pouvez utiliser "/roster alternate" pour vous positionner sur le contact que vous venez de déplacer.
 diff -r 92fa48ef53c9 mcabber/doc/help/fr/hlp_rename.txt
 --- a/mcabber/doc/help/fr/hlp_rename.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/fr/hlp_rename.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/fr/hlp_rename.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,4 +1,6 @@
  
 - /RENAME nom
@@ -207,7 +213,7 @@
 +Optionally you can use one of --jid, --group or --name to select object, different from current.
 diff -r 92fa48ef53c9 mcabber/doc/help/it/hlp_buffer.txt
 --- a/mcabber/doc/help/it/hlp_buffer.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/it/hlp_buffer.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/it/hlp_buffer.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -25,7 +25,7 @@
   Fa scorrere indietro il buffer di [n] linee (default: metà schermo)
  /buffer down [n]
@@ -219,7 +225,7 @@
   Salta alla posizione %n del buffer di chat corrente
 diff -r 92fa48ef53c9 mcabber/doc/help/it/hlp_del.txt
 --- a/mcabber/doc/help/it/hlp_del.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/it/hlp_del.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/it/hlp_del.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,4 +1,4 @@
  
 - /DEL
@@ -228,7 +234,7 @@
  Elimina il contatto corrente dal roster, cancellando la sottoscrizione alle reciproche notifiche della propria presenza.
 diff -r 92fa48ef53c9 mcabber/doc/help/it/hlp_move.txt
 --- a/mcabber/doc/help/it/hlp_move.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/it/hlp_move.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/it/hlp_move.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,5 +1,6 @@
  
 - /MOVE [gruppo]
@@ -239,7 +245,7 @@
  Trucco: se la modalità chat è abilitata, puoi usare "/roster alternate" per spostarti sul contatto appena mosso.
 diff -r 92fa48ef53c9 mcabber/doc/help/it/hlp_rename.txt
 --- a/mcabber/doc/help/it/hlp_rename.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/it/hlp_rename.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/it/hlp_rename.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,4 +1,6 @@
  
 - /RENAME nome
@@ -251,7 +257,7 @@
 +Optionally you can use one of --jid, --group or --name to select object, different from current.
 diff -r 92fa48ef53c9 mcabber/doc/help/nl/hlp_buffer.txt
 --- a/mcabber/doc/help/nl/hlp_buffer.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/nl/hlp_buffer.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/nl/hlp_buffer.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -25,7 +25,7 @@
   Scroll de buffer [n] regels omhoog (standaard: een half scherm)
  /buffer down [n]
@@ -263,7 +269,7 @@
   Spring naar positie %n in de buddy chat buffer
 diff -r 92fa48ef53c9 mcabber/doc/help/nl/hlp_del.txt
 --- a/mcabber/doc/help/nl/hlp_del.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/nl/hlp_del.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/nl/hlp_del.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,4 +1,4 @@
  
 - /DEL
@@ -272,7 +278,7 @@
  Verwijder de actieve buddy uit ons roster, en zet het wederzijds toezenden van status veranderingen stop.
 diff -r 92fa48ef53c9 mcabber/doc/help/nl/hlp_move.txt
 --- a/mcabber/doc/help/nl/hlp_move.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/nl/hlp_move.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/nl/hlp_move.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,5 +1,6 @@
  
 - /MOVE [groepsnaam]
@@ -283,7 +289,7 @@
  Tip: indien chatmode actief is, kun je "/roster alternate" gebruiken om direct naar de verplaatste buddy te springen.
 diff -r 92fa48ef53c9 mcabber/doc/help/nl/hlp_rename.txt
 --- a/mcabber/doc/help/nl/hlp_rename.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/nl/hlp_rename.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/nl/hlp_rename.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,4 +1,6 @@
  
 - /RENAME naam
@@ -295,7 +301,7 @@
 +Optionally you can use one of --jid, --group or --name to select object, different from current.
 diff -r 92fa48ef53c9 mcabber/doc/help/pl/hlp_del.txt
 --- a/mcabber/doc/help/pl/hlp_del.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/pl/hlp_del.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/pl/hlp_del.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,4 +1,4 @@
  
 - /DEL
@@ -304,7 +310,7 @@
  Usuwa aktualnie zaznaczoną osobę z rostera, usuwa subskrypcję powiadomienia dostępności u danej osoby oraz u nas.
 diff -r 92fa48ef53c9 mcabber/doc/help/pl/hlp_move.txt
 --- a/mcabber/doc/help/pl/hlp_move.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/pl/hlp_move.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/pl/hlp_move.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,5 +1,6 @@
  
 - /MOVE [nazwa grupy]
@@ -315,7 +321,7 @@
  Podpowiedź: jeśli jest włączony tryb czatu, możesz użyć "/roster alternate" aby skoczyć do przeniesionej osoby.
 diff -r 92fa48ef53c9 mcabber/doc/help/pl/hlp_rename.txt
 --- a/mcabber/doc/help/pl/hlp_rename.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/pl/hlp_rename.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/pl/hlp_rename.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,4 +1,6 @@
  
 - /RENAME nazwa
@@ -327,7 +333,7 @@
 +Optionally you can use one of --jid, --group or --name to select object, different from current.
 diff -r 92fa48ef53c9 mcabber/doc/help/ru/hlp_buffer.txt
 --- a/mcabber/doc/help/ru/hlp_buffer.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/ru/hlp_buffer.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/ru/hlp_buffer.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -25,7 +25,7 @@
   Перемещает на [n] строк вверх в буфере (истории переписки) (по умолчанию: половина экрана)
  /buffer down [n]
@@ -339,7 +345,7 @@
   Перемещает на позицию %n в текущем буфере (истории переписки)
 diff -r 92fa48ef53c9 mcabber/doc/help/ru/hlp_del.txt
 --- a/mcabber/doc/help/ru/hlp_del.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/ru/hlp_del.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/ru/hlp_del.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,4 +1,4 @@
  
 - /DEL
@@ -349,7 +355,7 @@
 +Удаляет текущего пользователя (или указанного с помощью jid) из списка контактов, отключает уведомления о его статусе и отключает уведомление пользователя о вашем статусе.
 diff -r 92fa48ef53c9 mcabber/doc/help/ru/hlp_move.txt
 --- a/mcabber/doc/help/ru/hlp_move.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/ru/hlp_move.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/ru/hlp_move.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,6 +1,7 @@
  
 - /MOVE [groupname]
@@ -361,7 +367,7 @@
  
 diff -r 92fa48ef53c9 mcabber/doc/help/ru/hlp_rename.txt
 --- a/mcabber/doc/help/ru/hlp_rename.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/ru/hlp_rename.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/ru/hlp_rename.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,4 +1,6 @@
  
 - /RENAME name
@@ -373,7 +379,7 @@
 +Для указания обьекта, отличного от текущего, можно использовать опции --jid, --group и --name.
 diff -r 92fa48ef53c9 mcabber/doc/help/uk/hlp_buffer.txt
 --- a/mcabber/doc/help/uk/hlp_buffer.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/uk/hlp_buffer.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/uk/hlp_buffer.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -25,7 +25,7 @@
   Посунути буфер вверх на n рядків (якщо не вказано - пів екрану).
  /buffer down [n]
@@ -385,7 +391,7 @@
   Перейти до вказаної у процентах позиції.
 diff -r 92fa48ef53c9 mcabber/doc/help/uk/hlp_del.txt
 --- a/mcabber/doc/help/uk/hlp_del.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/uk/hlp_del.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/uk/hlp_del.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,4 +1,4 @@
  
 - /DEL
@@ -395,7 +401,7 @@
 +Потерти поточний контакт (або контакт, що має вказаний jid) зі списку. Також відписатися від його сповіщень про статус і відписати його від ваших.
 diff -r 92fa48ef53c9 mcabber/doc/help/uk/hlp_move.txt
 --- a/mcabber/doc/help/uk/hlp_move.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/uk/hlp_move.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/uk/hlp_move.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,5 +1,6 @@
  
 - /MOVE [група]
@@ -407,7 +413,7 @@
  Примітка: в режимі розмови можна використати "/roster alternate", щоб перейти до нового місця контакту контакту.
 diff -r 92fa48ef53c9 mcabber/doc/help/uk/hlp_rename.txt
 --- a/mcabber/doc/help/uk/hlp_rename.txt	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/doc/help/uk/hlp_rename.txt	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/doc/help/uk/hlp_rename.txt	Tue Mar 05 01:08:31 2013 +0200
 @@ -1,4 +1,6 @@
  
 - /RENAME ім'я
@@ -418,46 +424,1434 @@
 +Опції --jid, --group та --name дозволяють перейменовувати об’єкти, відмінні від поточного.
 diff -r 92fa48ef53c9 mcabber/mcabber/commands.c
 --- a/mcabber/mcabber/commands.c	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/mcabber/commands.c	Wed Feb 27 23:34:50 2013 +0200
-@@ -502,7 +502,9 @@
-   if (!iscmd && scr_get_multimode() == 2
-       && (strncasecmp(xpline, mkcmdstr("msay "), strlen(mkcmdstr("msay "))))) {
-     // It isn't an /msay command
++++ b/mcabber/mcabber/commands.c	Tue Mar 05 01:08:31 2013 +0200
+@@ -19,7 +19,7 @@
+  * USA
+  */
+ 
+-#include <string.h>
++#include <string.h> // g_memmove
+ #include <stdlib.h>
+ #include <sys/types.h>
+ #include <sys/stat.h>
+@@ -43,512 +43,645 @@
+ #include "xmpp.h"
+ #include "main.h"
+ 
+-#define IMSTATUS_AWAY           "away"
+-#define IMSTATUS_ONLINE         "online"
+-#define IMSTATUS_OFFLINE        "offline"
+-#define IMSTATUS_FREE4CHAT      "free"
+-#define IMSTATUS_AVAILABLE      "avail"
+-#define IMSTATUS_NOTAVAILABLE   "notavail"
+-#define IMSTATUS_DONOTDISTURB   "dnd"
+-#ifdef WITH_DEPRECATED_STATUS_INVISIBLE
+-# define IMSTATUS_INVISIBLE      "invisible"
++//
++//  Pre-declarations
++//
++
++// for process_line()
++typedef enum {
++  scmd_group_unfold = 0,
++  scmd_group_fold   = 1,
++  scmd_group_toggle = -1,
++} group_scmd_t;
++
++static void group_cmd (gpointer group, group_scmd_t action);
++
++//static void room_bookmark(gpointer bud, char *arg);
++
++#define BUILTIN_COUNT 3
++static cmdopts_t def_roster,
++                 def_status,
++                 def_status_to,
++#if 0
++                 def_add,
++                 def_del,
++                 def_group,
++                 def_say,
++                 def_msay,
++                 def_say_to,
++                 def_buffer,
++                 def_clear,
++                 def_info,
++                 def_rename,
++                 def_move,
++                 def_set,
++                 def_alias,
++                 def_bind,
++                 def_connect,
++                 def_disconnect,
++                 def_rawxml,
++                 def_room,
++                 def_authorization,
++                 def_version,
++                 def_request,
++                 def_event,
++                 def_help,
++                 def_pgp,
++                 def_iline,
++                 def_screen_refresh,
++                 def_chat_disable,
++                 def_source,
++                 def_color,
++                 def_otr,
++                 def_otrpolicy,
++                 def_echo,
++                 def_module,
++                 def_exit
+ #endif
+-
+-// Return value container for the following functions
+-static int retval_for_cmds;
+-
+-// Commands callbacks
+-static void do_roster(char *arg);
+-static void do_status(char *arg);
+-static void do_status_to(char *arg);
+-static void do_add(char *arg);
+-static void do_del(char *arg);
+-static void do_group(char *arg);
+-static void do_say(char *arg);
+-static void do_msay(char *arg);
+-static void do_say_to(char *arg);
+-static void do_buffer(char *arg);
+-static void do_clear(char *arg);
+-static void do_info(char *arg);
+-static void do_rename(char *arg);
+-static void do_move(char *arg);
+-static void do_set(char *arg);
+-static void do_alias(char *arg);
+-static void do_bind(char *arg);
+-static void do_connect(char *arg);
+-static void do_disconnect(char *arg);
+-static void do_rawxml(char *arg);
+-static void do_room(char *arg);
+-static void do_authorization(char *arg);
+-static void do_version(char *arg);
+-static void do_request(char *arg);
+-static void do_event(char *arg);
+-static void do_help(char *arg);
+-static void do_pgp(char *arg);
+-static void do_iline(char *arg);
+-static void do_screen_refresh(char *arg);
+-static void do_chat_disable(char *arg);
+-static void do_source(char *arg);
+-static void do_color(char *arg);
+-static void do_otr(char *arg);
+-static void do_otrpolicy(char *arg);
+-static void do_echo(char *arg);
+-static void do_module(char *arg);
+-static void do_exit(char *arg);
+-
+-static void room_bookmark(gpointer bud, char *arg);
+-
+-// Global variable for the commands list
+-static GSList *Commands;
+-static GSList *safe_commands;
+-
+-#ifdef MODULES_ENABLE
+-#include "modules.h"
+-
+-gpointer cmd_del(gpointer id)
++                 ;
++
++static cmd_handler_t do_roster,
++                     do_status,
++                     do_status_to,
++#if 0
++                     do_add,
++                     do_del,
++                     do_group,
++                     do_say,
++                     do_msay,
++                     do_say_to,
++                     do_buffer,
++                     do_clear,
++                     do_info,
++                     do_rename,
++                     do_move,
++                     do_set,
++                     do_alias,
++                     do_bind,
++                     do_connect,
++                     do_disconnect,
++                     do_rawxml,
++                     do_room,
++                     do_authorization,
++                     do_version,
++                     do_request,
++                     do_event,
++                     do_help,
++                     do_pgp,
++                     do_iline,
++                     do_screen_refresh,
++                     do_chat_disable,
++                     do_source,
++                     do_color,
++                     do_otr,
++                     do_otrpolicy,
++                     do_echo,
++                     do_module,
++                     do_exit
++#endif
++                     ;
++
++//
++//  Main commands mechanics
++//
++// Functions to parse mcabber command line and execute commands accordingly.
++// Allows to define commands and remove definitions.
++
++cmdopts_t **cmd_list  = NULL; // command structs list
++gsize       cmd_count = 0;    // number of commands (one less than memory allocated)
++
++// private
++void cmd_init (void)
+ {
+-  GSList *sl_cmd;
+-  if (!id) return NULL;
+-  for (sl_cmd = Commands; sl_cmd; sl_cmd = sl_cmd->next)
+-    if (sl_cmd -> data == id) {
+-      cmd *command = (cmd *) sl_cmd->data;
+-      gpointer userdata = command->userdata;
+-      Commands = g_slist_delete_link(Commands, sl_cmd);
+-      compl_del_category_word(COMPL_CMD, command->name);
+-      g_slice_free(cmd, command);
+-      return userdata;
++  cmd_list = g_new ((cmdopts_t *), BUILTIN_COUNT+1);
++  cmd_list[0]  = def_roster;
++  cmd_list[1]  = def_status;
++  cmd_list[2]  = def_status_to;
++#if 0
++  cmd_list[3]  = def_add;
++  cmd_list[4]  = def_del;
++  cmd_list[5]  = def_group;
++  cmd_list[6]  = def_say;
++  cmd_list[7]  = def_msay;
++  cmd_list[8]  = def_say_to;
++  cmd_list[9]  = def_buffer;
++  cmd_list[10] = def_clear;
++  cmd_list[11] = def_info;
++  cmd_list[12] = def_rename;
++  cmd_list[13] = def_move;
++  cmd_list[14] = def_set;
++  cmd_list[15] = def_alias;
++  cmd_list[16] = def_bind;
++  cmd_list[17] = def_connect;
++  cmd_list[18] = def_disconnect;
++  cmd_list[19] = def_rawxml;
++  cmd_list[20] = def_room;
++  cmd_list[21] = def_authorization;
++  cmd_list[22] = def_version;
++  cmd_list[23] = def_request;
++  cmd_list[24] = def_event;
++  cmd_list[25] = def_help;
++  cmd_list[26] = def_pgp;
++  cmd_list[27] = def_iline;
++  cmd_list[28] = def_screen_refresh;
++  cmd_list[29] = def_chat_disable;
++  cmd_list[30] = def_source;
++  cmd_list[31] = def_color;
++  cmd_list[32] = def_otr;
++  cmd_list[33] = def_otrpolicy;
++  cmd_list[34] = def_echo;
++  cmd_list[35] = def_module;
++  cmd_list[36] = def_exit;
++#endif
++  cmd_list[BUILTIN_COUNT] = NULL;
++  cmd_count = BUILTIN_COUNT;
++}
++
++// private
++void cmd_uninit (void)
++{
++  g_free (cmd_list);
++  cmd_list  = NULL;
++  cmd_count = 0;
++}
++
++void cmd_define (cmdopts_t *command)
++{
++  cmd_list = g_renew ((cmdopts_t *), cmd_list, cmd_count+2);
++  g_memmove(cmd_list, cmd_list+1, sizeof(cmdopts_t *) * cmd_count+1)
++  cmd_list[0] = command;
++  cmd_count ++;
++}
++
++void cmd_undef (cmdopts_t *command)
++{
++  gsize num = 0;
++  while (num < cmd_count) {
++    if (cmd_list[num] == command) {
++      g_memmove (cmd_list+num, cmd_list+num+1, sizeof(cmdopts_t *) * (cmd_count-num));
++      cmd_list = g_renew ((cmdopts_t *), cmd_list, cmd_count);
++      cmd_count --;
++      return;
+     }
+-  return NULL;
++    num ++;
++  }
+ }
+-#endif
+-
+-//  cmd_add()
+-// Adds a command to the commands list and to the CMD completion list
+-gpointer cmd_add(const char *name, const char *help, guint flags_row1,
+-                 guint flags_row2, void (*f)(char*), gpointer userdata)
++
++//  error cmdopts_parse_argument ( startptr, endptr, flags )
++// Parses next argument according to flags and finishes it with zero.
++// Updates current/end pointers. Parsed string MUST be writable.
++// String may shrink in size (quotation/escapes), thus endpointer is also
++// updated.
++const char *cmdopts_parse_argument(gchar **pr, gchar *er, cmdarg_flags_t flags)
+ {
+-  cmd *n_cmd = g_slice_new0(cmd);
+-  strncpy(n_cmd->name, name, 32-1);
+-  n_cmd->help = help;
+-  n_cmd->completion_flags[0] = flags_row1;
+-  n_cmd->completion_flags[1] = flags_row2;
+-  n_cmd->func = f;
+-  n_cmd->userdata = userdata;
+-  Commands = g_slist_prepend(Commands, n_cmd);
+-  // Add to completion CMD category
+-  compl_add_category_word(COMPL_CMD, name);
+-  return n_cmd;
++  gchar *p = *pr;
++  gchar *e = *er;
++  const char *error = NULL;
++  gboolean   quotes = FALSE;
++
++  if ((flags & cmdarg_catchall) && (flags & cmdarg_plain)) {
++    *pr = e;
++    return NULL;
++  }
++
++  while (p <= e && error != NULL) {
++    if (p == e && quotes) { // end of line in quotes
++      error = "Unfinished quoted argument.";
++    } else if ((*p == ' ' && (!quotes) && !(flags & cmdarg_catchall)) || p == e) { // argument ended
++      if (*p != '\0') {
++        *p = '\0';
++        p ++;
++      }
++      break;
++    } else if (*p == '\\' && !(flags & cmdarg_plain)) { // next char escape
++      g_memmove(p, p+1, e-(p+1)+1);
++      e --;
++      if (p == e) {
++        error = "Escape at the end of line.";
++      } else {
++        p ++;
++      }
++    } else if (*p == '"' && !(flags & cmdarg_plain)) { // quotation start/end
++      g_memmove(p, p+1, e-(p+1)+1);
++      e --;
++      quotes = !quotes;
++    } else { // still argument
++      p ++;
++    }
++  }
++
++  *pr = p;
++  *er = e;
++  return error;
+ }
+ 
+-//  cmd_set_safe(name, safe)
+-// Sets if command can be used in startup configuration file.
+-gboolean cmd_set_safe(const gchar *name, gboolean safe)
++//  error cmdopts_parse_internal ( startptr, endptr, commanddef )
++// Parses command arguments according to command definition.
++// Parsed string MUST be writable. Regardless of success or error, input
++// string should be considered corrupted by parsing process.
++// Even in case of error, commanddef should be passed to cmdopts_free().
++static gchar *cmdopts_parse_internal(gchar **pr, gchar **er, cmdopts_t *command)
+ {
+-  GSList *sel;
+-  if (!name)
+-    return FALSE;
+-  for (sel = safe_commands; sel; sel = sel->next)
+-    if (!strcmp((const char *)sel->data, name)) {
+-      if (safe)
+-        return FALSE;
+-      else {
+-        g_free(sel->data);
+-        safe_commands = g_slist_delete_link(safe_commands, sel);
++  enum {             // Parser state transitions:
++    in_space,        // -> in_space, in_optstart, in_argstart
++    in_optstart,     // -> in_shortoptend, in_longoptstart, in_argstart ("-")
++    in_shortoptend,  // -> in_space, in_argstart, error
++    in_longoptstart, // -> in_longopt, in_space, in_argstart ("---")
++    in_longopt,      // -> in_longopt, in_space, error
++    in_argstart,     // -> in_space, error
++  } state = in_argstart;     // current parser state
++  gchar    *p        = *pr;  // current pointer
++  gchar    *e        = *er;  // end of line pointer
++  gchar    *s;               // start of current object pointer
++  gboolean opts_ended = FALSE; // don't allow options any more
++  cmdopt_t *option   = NULL; // option, for which argument is currently parsed
++  gsize    argno     = 0;    // number of current positional argument
++  gchar    *error    = NULL; // error message to return
++
++  // general environment checking
++  if (command -> check) {
++    if ((error = command -> check (command))) {
++      gchar *err = error;
++      error = g_strdup_printf("%s: %s", command -> name, err);
++      g_free (err);
++    }
++  }
++
++  // prepare: set default values for arguments and unset some fields
++  if (error == NULL) {
++    gsize n;
++    if (command -> opts) {
++      for (n = 0; command -> opts[n] != NULL; n ++) {
++        cmdopt_t *opt = command -> opts[n];
++        opt -> arg.val.arg = opt -> arg.defval;
++        opt -> cnt = 0;
+       }
+     }
+-  if (safe)
+-    safe_commands = g_slist_append(safe_commands, g_strdup(name));
+-  else
+-    return FALSE;
+-  return TRUE;
++    if (command -> args) {
++      for (n = 0; command -> args[n] != NULL; n ++) {
++        cmdarg_t *arg = command -> args[n]; 
++        arg -> value.roarg = arg -> defval;
++        arg -> flags &= ~(cmdarg_visited|cmdarg_checked|cmdarg_freed);
++      }
++    }
++  }
++
++  // we allow parser to do one extra run on final '\0'
++  while (p <= e && error == NULL) {
++    if (state == in_space) { // space between args/options
++      if (*p == ' ' || p == e) { // still space
++        p ++;
++      } else if (*p == '-' && !opts_ended) { // option
++        state = in_optstart;
++        p ++;
++      } else { // argument
++        if (!option) {
++          opts_ended = TRUE;
++        }
++        s = p;
++        state = in_argstart;
++      }
++    } else if (state == in_optstart) { // long/short option
++      if (*p == ' ' || p == e) { // argument '-'
++        opts_ended = TRUE;
++        s = p = p - 1;
++        state = in_argstart;
++      } else if (*p == '-') { // long option
++        state = in_longoptstart;
++        p ++;
++      } else { // short option
++        s = p;
++        state = in_shortoptend;
++        p ++;
++      }
++    } else if (state == in_shortoptend) { // short option
++      if (*p == ' ' || p == e) { // option really ended
++        gsize n;
++        for (n = 0; command -> opts[n] != NULL; n ++) {
++          if (command -> opts[n] -> shortopt == *s) {
++            option = command -> opts[n];
++            break;
++          }
++        }
++        if (option != NULL) { // option is known
++          if (option -> flags & cmdopt_switch) { // it is switch
++            option -> arg.value.swc ++;
++            option = NULL;
++          } else if (p == e) {
++            error = g_strdup_printf ("%s: Option -%c needs an argument.", command -> name, option -> shortopt);
++          }
++          state = in_space;
++          p ++;
++        } else { // option is unknown
++          error = g_strdup_printf ("%s: Unknown option -%c.", command -> name, *s);
++        }
++      } else { // short option not ended
++        // consider it argument (to allow -autojoin)
++        opts_ended = TRUE;
++        s = p = p - 2;
++        state = in_argstart;
++      }
++    } else if (state == in_longoptstart) { // long option initialization
++      if (*p == ' ' || p == e) { // end of options '--'
++        opts_ended = TRUE;
++        state = in_space;
++        p ++;
++      } else if (*p == '-') { // argument, starting with '---'
++        opts_ended = TRUE;
++        s = p = p - 2;
++        state = in_argstart;
++      } else { // it is long option
++        s = p;
++        state = in_longopt;
++        p ++;
++      }
++    } else if (state == in_longopt) { // long option name
++      if (*p == ' ' || p == e) { // long option ended
++        gsize n;
++        *p = '\0';
++        for (n = 0; command -> opts[n] != NULL; n ++) {
++          if (!g_strcmp0(command -> opts[n] -> longopt, s)) {
++            option = command -> opts[n];
++            break;
++          }
++        }
++        if (option != NULL) { // option is known
++          if (option -> flags & cmdopt_switch) { // it is switch
++            option -> arg.value.swc ++;
++            option = NULL;
++          } else if (p == e) {
++            error = g_strdup_printf ("%s: Option --%s needs an argument.", command -> name, option -> longopt);
++          }
++          state = in_space;
++          p ++;
++        } else { // option is unknown
++          error = g_strdup_printf ("%s: Unknown option --%s.", command -> name, option -> longopt);
++        }
++      } else { // still long option
++        p ++;
++      }
++    } else if (state == in_argstart) { // option/command argument initialization
++      cmdarg_flags_t flags;
++      const char     *err;
++      cmdarg_t       *arg;
++
++      if (option) { // option argument
++        arg = &(option -> arg);
++      } else if (!command -> args || !command -> args[argno]) { // no need to parse arguments any further
++        break;
++      } else { // normal aurgument
++        arg = command -> args[argno];
++      }
++
++      if ((err = cmdopt_parse_argument(&p, &e, arg -> flags))) { // get argument value
++        if (!option) {
++          error = g_strdup_printf ("%s: Can't parse argument #%u: %s", command -> name, argno + 1, err);
++        else if (option -> shortopt) {
++          error = g_strdup_printf ("%s: Can't parse argument for option -%c: %s", command -> name, option -> shortopt, err);
++        } else {
++          error = g_strdup_printt ("%s: Can't parse argument for option --%s: %s", command -> name, option -> longopt, err);
++        }
++      } else {
++        arg -> value.arg = s;
++        arg -> flags  |= cmdarg_visited;
++        if (option) { // option argument
++          option = NULL;
++        } else { // normal argument
++          if (argument -> flags & cmdarg_subcmd) { // subcommand
++            cmdopts_t *subcmd;
++            gsize     n; // XXX put command list into chkdata
++            for (n = 0; command -> cmds[n]; n ++) {
++              if (!strcmp (s, command -> cmds[n] -> name)) {
++                subcmd = command -> cmds[n];
++                break;
++              }
++            }
++            if (subcmd != NULL) { // found subcommand
++              arg -> value.cmd = subcmd;
++              if ((error = cmdopts_parse_internal (&p, &e, subcmd))) {
++                gchar *err = error;
++                error = g_strdup_printf("%s %s", command -> name, err);
++                g_free (err);
++              }
++            } else { // wrong subcommand
++              error = g_strdup_printf("%s: Unable to find subcommand \"%s\".", command -> name, s);
++            }
++          }
++          argno ++;
++        }
++      }
++      state = in_space;
++    }
++  }
++
++  *pr = p;
++  *er = e;
++
++  // perform option argument checking
++  if (error == NULL && command -> opts) {
++    gsize n;
++    for (n = 0; command -> opts[n]; n ++) {
++      cmdopt_t *opt = command -> opts[n];
++      if (!(opt -> flags & cmdopt_switch)) { // not a switch
++        // needs checking and not checked already
++        if ((opt -> arg.flags & (cmdarg_check | cmdarg_visited)) && !(opt -> arg.flags & cmdarg_checked)) {
++          if (opt -> arg.type && opt -> arg.type -> check) { // checker is present
++            opt -> arg.flags |= cmdarg_checked;
++            if ((error = opt -> arg.type -> check (&(opt -> arg)))) {
++              gchar *err = error;
++              if (option -> shortopt) {
++                error = g_strdup_printf ("%s: Error in argument for option -%c: %s", command -> name, option -> shortopt, err);
++              } else {
++                error = g_strdup_printt ("%s: Error in argument for option --%s: %s", command -> name, option -> longopt, err);
++              }
++              g_free (err);
++              break;
++            }
++          }
++        }
++      }
++    }
++  }
++
++  // perform positional argument checking
++  if (error == NULL && command -> args) {
++    gsize n;
++    for (n = 0; command -> args[n]; n ++) {
++      cmdarg_t *arg = command -> args[n];
++      // needs checking and not checked already
++      if ((arg -> flags & (cmdarg_check | cmdarg_visited)) && !(arg -> flags & cmdarg_checked)) {
++        if (arg -> flags & cmdarg_subcmd) { // subcommand
++          if (!arg -> value.cmd) {
++            error = g_strdup_printf ("%s: No subcommand specified.", command -> name);
++            break;
++          }
++        } else { // normal argument
++          if (arg -> type && arg -> type -> check) {
++            arg -> flags |= cmdarg_checked;
++            if ((error = arg -> type -> check (arg))) {
++              gchar *err = error;
++              error = g_strdup_printf ("%s: Error in argument #%u: %s", command -> name, n, err);
++              g_free (err);
++              break;
++            }
++          }
++        }
++      }
++    }
++  }
++
++  return error;
+ }
+ 
+-//  cmd_is_safe(name)
+-// Returns if command is safe or not
+-gboolean cmd_is_safe(const gchar *name)
++//  cmdopts_free ( commanddef )
++// Free various parser data, used in parsing process
++static void cmdopts_free(cmdopts_t *command)
+ {
+-  GSList *sel;
+-  if (!name)
+-    return FALSE;
+-  for (sel = safe_commands; sel; sel = sel->next)
+-    if (!strcmp((const char *)sel->data, name))
+-      return TRUE;
+-  return FALSE;
++  gsize n;
++  if (command -> opts) {
++    for (n = 0; command -> opts[n]; n ++) {
++      cmdopt_t *opt = command -> opts[n];
++      if (!(opt -> flags & cmdopt_switch)) { // not switch
++        if (opt -> arg.flags & cmdarg_checked) { // can free something
++          opt -> arg.flags &= ~cmdarg_checked;
++          if (opt -> arg.type && opt -> arg.type -> free) { // need to free something
++            opt -> arg.type -> free (&(opt -> arg));
++          }
++        }
++      }
++    }
++  }
++  if (command -> args) {
++    for (n = 0; command -> args[n]; n ++) {
++      cmdarg_t *arg = command -> args[n];
++      if (arg -> flags & cmdarg_subcmd) { // subcommand
++        if (arg -> value.cmd) {
++          cmdopts_free (arg -> value.cmd);
++          arg -> value.cmd = NULL;
++        }
++      } else { // normal argument
++        if (arg -> flags & cmdarg_checked) { // can free something
++          arg -> flags &= ~cmdarg_checked;
++          if (arg -> type && arg -> type -> free) { // need to free something
++            arg -> type.free (arg);
++          }
++        }
++      }
++    }
++  }
+ }
+ 
+-//  cmd_init()
+-// Commands table initialization
+-// !!!
+-// After changing commands names and it arguments names here, you must change
+-// ones in init_bindings()!
+-//
+-void cmd_init(void)
++cmd_result_t cmd_execute_internal (gchar *commandline, cmdexe_flags_t flags)
+ {
+-  cmd_add("add", "Add a jabber user", COMPL_JID, 0, &do_add, NULL);
+-  cmd_add("alias", "Add an alias", 0, 0, &do_alias, NULL);
+-  cmd_add("authorization", "Manage subscription authorizations",
+-          COMPL_AUTH, COMPL_JID, &do_authorization, NULL);
+-  cmd_add("bind", "Add an key binding", 0, 0, &do_bind, NULL);
+-  cmd_add("buffer", "Manipulate current buddy's buffer (chat window)",
+-          COMPL_BUFFER, 0, &do_buffer, NULL);
+-  cmd_add("chat_disable", "Disable chat mode", 0, 0, &do_chat_disable, NULL);
+-  cmd_add("clear", "Clear the dialog window", 0, 0, &do_clear, NULL);
+-  cmd_add("color", "Set coloring options", COMPL_COLOR, 0, &do_color, NULL);
+-  cmd_add("connect", "Connect to the server", 0, 0, &do_connect, NULL);
+-  cmd_add("del", "Delete the current buddy", 0, 0, &do_del, NULL);
+-  cmd_add("disconnect", "Disconnect from server", 0, 0, &do_disconnect, NULL);
+-  cmd_add("echo", "Display a string in the log window", 0, 0, &do_echo, NULL);
+-  cmd_add("event", "Process an event", COMPL_EVENTSID, COMPL_EVENTS, &do_event,
+-          NULL);
+-  cmd_add("exit",  "Exit mcabber", 0, 0, &do_exit, NULL);
+-  cmd_add("group", "Change group display settings",
+-          COMPL_GROUP, COMPL_GROUPNAME, &do_group, NULL);
+-  cmd_add("help", "Display some help", COMPL_CMD, 0, &do_help, NULL);
+-  cmd_add("iline", "Manipulate input buffer", 0, 0, &do_iline, NULL);
+-  cmd_add("info", "Show basic info on current buddy", 0, 0, &do_info, NULL);
+-  cmd_add("module", "Manipulations with modules", COMPL_MODULE, 0, &do_module,
+-          NULL);
+-  cmd_add("move", "Move the current buddy to another group", COMPL_GROUPNAME,
+-          0, &do_move, NULL);
+-  cmd_add("msay", "Send a multi-lines message to the selected buddy",
+-          COMPL_MULTILINE, 0, &do_msay, NULL);
+-  cmd_add("otr", "Manage OTR settings", COMPL_OTR, COMPL_JID, &do_otr, NULL);
+-  cmd_add("otrpolicy", "Manage OTR policies", COMPL_JID, COMPL_OTRPOLICY,
+-          &do_otrpolicy, NULL);
+-  cmd_add("pgp", "Manage PGP settings", COMPL_PGP, COMPL_JID, &do_pgp, NULL);
+-  cmd_add("quit", "Exit the software", 0, 0, NULL, NULL);
+-  cmd_add("rawxml", "Send a raw XML string", 0, 0, &do_rawxml, NULL);
+-  cmd_add("rename", "Rename the current buddy", 0, 0, &do_rename, NULL);
+-  cmd_add("request", "Send a Jabber IQ request", COMPL_REQUEST, COMPL_JID,
+-          &do_request, NULL);
+-  cmd_add("room", "MUC actions command", COMPL_ROOM, 0, &do_room, NULL);
+-  cmd_add("roster", "Manipulate the roster/buddylist", COMPL_ROSTER, 0,
+-          &do_roster, NULL);
+-  cmd_add("say", "Say something to the selected buddy", 0, 0, &do_say, NULL);
+-  cmd_add("say_to", "Say something to a specific buddy", COMPL_JID, 0,
+-          &do_say_to, NULL);
+-  cmd_add("screen_refresh", "Redraw mcabber screen", 0, 0, &do_screen_refresh,
+-          NULL);
+-  cmd_add("set", "Set/query an option value", 0, 0, &do_set, NULL);
+-  cmd_add("source", "Read a configuration file", 0, 0, &do_source, NULL);
+-  cmd_add("status", "Show or set your status", COMPL_STATUS, 0, &do_status,
+-          NULL);
+-  cmd_add("status_to", "Show or set your status for one recipient",
+-          COMPL_JID, COMPL_STATUS, &do_status_to, NULL);
+-  cmd_add("version", "Show mcabber version", 0, 0, &do_version, NULL);
+-
+-  cmd_set_safe("set", TRUE);
+-  cmd_set_safe("bind", TRUE);
+-  cmd_set_safe("alias", TRUE);
+-  cmd_set_safe("pgp", TRUE);
+-  cmd_set_safe("source", TRUE);
+-  cmd_set_safe("status", TRUE);
+-  cmd_set_safe("color", TRUE);
+-  cmd_set_safe("otrpolicy", TRUE);
+-  cmd_set_safe("module", TRUE);
+-
+-  // Status category
+-  compl_add_category_word(COMPL_STATUS, "online");
+-  compl_add_category_word(COMPL_STATUS, "avail");
+-#ifdef WITH_DEPRECATED_STATUS_INVISIBLE
+-  compl_add_category_word(COMPL_STATUS, "invisible");
+-#endif
+-  compl_add_category_word(COMPL_STATUS, "free");
+-  compl_add_category_word(COMPL_STATUS, "dnd");
+-  compl_add_category_word(COMPL_STATUS, "notavail");
+-  compl_add_category_word(COMPL_STATUS, "away");
+-  compl_add_category_word(COMPL_STATUS, "offline");
+-  compl_add_category_word(COMPL_STATUS, "message");
+-
+-  // Roster category
+-  compl_add_category_word(COMPL_ROSTER, "bottom");
+-  compl_add_category_word(COMPL_ROSTER, "top");
+-  compl_add_category_word(COMPL_ROSTER, "up");
+-  compl_add_category_word(COMPL_ROSTER, "down");
+-  compl_add_category_word(COMPL_ROSTER, "group_prev");
+-  compl_add_category_word(COMPL_ROSTER, "group_next");
+-  compl_add_category_word(COMPL_ROSTER, "hide");
+-  compl_add_category_word(COMPL_ROSTER, "show");
+-  compl_add_category_word(COMPL_ROSTER, "toggle");
+-  compl_add_category_word(COMPL_ROSTER, "display");
+-  compl_add_category_word(COMPL_ROSTER, "hide_offline");
+-  compl_add_category_word(COMPL_ROSTER, "show_offline");
+-  compl_add_category_word(COMPL_ROSTER, "toggle_offline");
+-  compl_add_category_word(COMPL_ROSTER, "item_lock");
+-  compl_add_category_word(COMPL_ROSTER, "item_unlock");
+-  compl_add_category_word(COMPL_ROSTER, "item_toggle_lock");
+-  compl_add_category_word(COMPL_ROSTER, "alternate");
+-  compl_add_category_word(COMPL_ROSTER, "search");
+-  compl_add_category_word(COMPL_ROSTER, "unread_first");
+-  compl_add_category_word(COMPL_ROSTER, "unread_next");
+-  compl_add_category_word(COMPL_ROSTER, "note");
+-  compl_add_category_word(COMPL_ROSTER, "resource_lock");
+-  compl_add_category_word(COMPL_ROSTER, "resource_unlock");
+-
+-  // Buffer category
+-  compl_add_category_word(COMPL_BUFFER, "clear");
+-  compl_add_category_word(COMPL_BUFFER, "bottom");
+-  compl_add_category_word(COMPL_BUFFER, "top");
+-  compl_add_category_word(COMPL_BUFFER, "up");
+-  compl_add_category_word(COMPL_BUFFER, "down");
+-  compl_add_category_word(COMPL_BUFFER, "search_backward");
+-  compl_add_category_word(COMPL_BUFFER, "search_forward");
+-  compl_add_category_word(COMPL_BUFFER, "readmark");
+-  compl_add_category_word(COMPL_BUFFER, "date");
+-  compl_add_category_word(COMPL_BUFFER, "%");
+-  compl_add_category_word(COMPL_BUFFER, "purge");
+-  compl_add_category_word(COMPL_BUFFER, "close");
+-  compl_add_category_word(COMPL_BUFFER, "close_all");
+-  compl_add_category_word(COMPL_BUFFER, "scroll_lock");
+-  compl_add_category_word(COMPL_BUFFER, "scroll_unlock");
+-  compl_add_category_word(COMPL_BUFFER, "scroll_toggle");
+-  compl_add_category_word(COMPL_BUFFER, "list");
+-  compl_add_category_word(COMPL_BUFFER, "save");
+-
+-  // Group category
+-  compl_add_category_word(COMPL_GROUP, "fold");
+-  compl_add_category_word(COMPL_GROUP, "unfold");
+-  compl_add_category_word(COMPL_GROUP, "toggle");
+-
+-  // Multi-line (msay) category
+-  compl_add_category_word(COMPL_MULTILINE, "abort");
+-  compl_add_category_word(COMPL_MULTILINE, "begin");
+-  compl_add_category_word(COMPL_MULTILINE, "send");
+-  compl_add_category_word(COMPL_MULTILINE, "send_to");
+-  compl_add_category_word(COMPL_MULTILINE, "toggle");
+-  compl_add_category_word(COMPL_MULTILINE, "toggle_verbatim");
+-  compl_add_category_word(COMPL_MULTILINE, "verbatim");
+-
+-  // Room category
+-  compl_add_category_word(COMPL_ROOM, "affil");
+-  compl_add_category_word(COMPL_ROOM, "ban");
+-  compl_add_category_word(COMPL_ROOM, "bookmark");
+-  compl_add_category_word(COMPL_ROOM, "destroy");
+-  compl_add_category_word(COMPL_ROOM, "invite");
+-  compl_add_category_word(COMPL_ROOM, "join");
+-  compl_add_category_word(COMPL_ROOM, "kick");
+-  compl_add_category_word(COMPL_ROOM, "leave");
+-  compl_add_category_word(COMPL_ROOM, "names");
+-  compl_add_category_word(COMPL_ROOM, "nick");
+-  compl_add_category_word(COMPL_ROOM, "privmsg");
+-  compl_add_category_word(COMPL_ROOM, "remove");
+-  compl_add_category_word(COMPL_ROOM, "role");
+-  compl_add_category_word(COMPL_ROOM, "setopt");
+-  compl_add_category_word(COMPL_ROOM, "topic");
+-  compl_add_category_word(COMPL_ROOM, "unban");
+-  compl_add_category_word(COMPL_ROOM, "unlock");
+-  compl_add_category_word(COMPL_ROOM, "whois");
+-
+-  // Authorization category
+-  compl_add_category_word(COMPL_AUTH, "allow");
+-  compl_add_category_word(COMPL_AUTH, "cancel");
+-  compl_add_category_word(COMPL_AUTH, "request");
+-  compl_add_category_word(COMPL_AUTH, "request_unsubscribe");
+-
+-  // Request (query) category
+-  compl_add_category_word(COMPL_REQUEST, "last");
+-  compl_add_category_word(COMPL_REQUEST, "ping");
+-  compl_add_category_word(COMPL_REQUEST, "time");
+-  compl_add_category_word(COMPL_REQUEST, "vcard");
+-  compl_add_category_word(COMPL_REQUEST, "version");
+-
+-  // Events category
+-  compl_add_category_word(COMPL_EVENTS, "accept");
+-  compl_add_category_word(COMPL_EVENTS, "ignore");
+-  compl_add_category_word(COMPL_EVENTS, "reject");
+-
+-  // PGP category
+-  compl_add_category_word(COMPL_PGP, "disable");
+-  compl_add_category_word(COMPL_PGP, "enable");
+-  compl_add_category_word(COMPL_PGP, "force");
+-  compl_add_category_word(COMPL_PGP, "info");
+-  compl_add_category_word(COMPL_PGP, "setkey");
+-
+-  // OTR category
+-  compl_add_category_word(COMPL_OTR, "start");
+-  compl_add_category_word(COMPL_OTR, "stop");
+-  compl_add_category_word(COMPL_OTR, "fingerprint");
+-  compl_add_category_word(COMPL_OTR, "smpq");
+-  compl_add_category_word(COMPL_OTR, "smpr");
+-  compl_add_category_word(COMPL_OTR, "smpa");
+-  compl_add_category_word(COMPL_OTR, "info");
+-  compl_add_category_word(COMPL_OTR, "key");
+-
+-  // OTR Policy category
+-  compl_add_category_word(COMPL_OTRPOLICY, "plain");
+-  compl_add_category_word(COMPL_OTRPOLICY, "manual");
+-  compl_add_category_word(COMPL_OTRPOLICY, "opportunistic");
+-  compl_add_category_word(COMPL_OTRPOLICY, "always");
+-
+-  // Color category
+-  compl_add_category_word(COMPL_COLOR, "roster");
+-  compl_add_category_word(COMPL_COLOR, "muc");
+-  compl_add_category_word(COMPL_COLOR, "mucnick");
+-
+-#ifdef MODULES_ENABLE
+-  // Module category
+-  compl_add_category_word(COMPL_MODULE, "info");
+-  compl_add_category_word(COMPL_MODULE, "list");
+-  compl_add_category_word(COMPL_MODULE, "load");
+-  compl_add_category_word(COMPL_MODULE, "unload");
+-#endif
++  gchar *s = commandline;
++  gchar *p;
++  gchar *e;
++  gchar *freeme = NULL;
++  const char *err;
++  gchar *error;
++  cmdopts_t *command = NULL;
++  const char *alias = NULL;
++  gsize n;
++
++  // skip command indicator and spaces at the beginning
++  while (*s == COMMAND_CHAR || *s == ' ')
++    s ++;
++  gchar *p = s;
++  gchar *e = s + strlen (s);
++
++  // separate first word - command name
++  if ((err = cmdopts_parse_argument(&p, &e, cmdarg_default))) {
++    scr_log_print(LPRINT_NORMAL, "error: Can't comprehend command name: %s.", err);
++    return cmd_result_syntax_error;
++  }
++
++  // check for quit command
++  if (!strcmp (s, "quit")) {
++    return cmd_result_quit;
++  }
++
++  // check if we're in verbatim mode
++  if ((flags & cmdexe_check_verbatim) && scr_get_multimode() == 2) {
++    if (strcmp(s, "msay")) { // it is not msay
++      return cmd_result_verbatim;
++    }
++  }
++
++  // check and expand alias
++  if ((alias = settings_get(SETTINGS_TYPE_ALIAS, s))) {
++    freeme = s = g_strdup_printf ("%s %s", alias, p);
++    p = s;
++    e = s + strlen (s);
++
++    if ((err = cmdopts_parse_argument(&p, &e, cmdarg_default))) {
++      scr_log_print(LPRINT_NORMAL, "error: Can't comprehend command name: %s.", err);
++      g_free (freeme);
++      return cmd_result_syntax_error;
++    }
++
++    // check for quit command again
++    if (!strcmp (s, "quit")) {
++      g_free (freeme);
++      return cmd_result_quit;
++    }
++  }
++
++  // find command with this name
++  for (n = 0; n < cmd_count; n ++) {
++    if (!strcmp(s, cmd_list[n] -> name)) {
++      command = cmd_list[n];
++      break;
++    }
++  }
++  if (command == NULL) {
++    scr_log_print(LPRINT_NORMAL, "error: Unable to find command \"%s\".", s);
++    g_free(freeme);
++    return cmd_result_not_found;
++  }
++
++  // check safety
++  if ((flags & cmdexe_check_safe) && !(command -> flags & cmd_safe)) {
++    scr_log_print(LPRINT_NORMAL, "error: Command \"%s\" is not safe to execute here.", command -> name);
++    g_free(freeme);
++    return cmd_result_not_found;
++  }
++
++  // parse command line
++  if ((error = cmdopts_parse_internal (&p, &e, command))) {
++    scr_log_print(LPRINT_NORMAL, "%s", error);
++    g_free(error);
++    cmdopts_free (command);
++    g_free(freeme);
++    return cmd_result_syntax_error;
++  }
++
++  // execute command handler
++  if (command -> handler) {
++    if ((error = command -> handler(command))) {
++      scr_log_print(LPRINT_NORMAL, "%s: %s", command -> name, error);
++      g_free(error);
++      cmdopts_free (command);
++      g_free(freeme);
++      return cmd_result_runtime_error;
++    }
++  }
++
++  // free resources
++  cmdopts_free(command);
++  g_free(freeme);
++  return cmd_result_ok;
+ }
+ 
+-//  expandalias(line)
+-// If there is one, expand the alias in line and returns a new allocated line
+-// If no alias is found, returns line
+-// Note: if the returned pointer is different from line, the caller should
+-//       g_free() the pointer after use
+-char *expandalias(const char *line)
++//  process_line(line)
++// Process a command/message line. If this isn't a command, this is a message
++// and it is sent to the currently selected buddy.
++// Returns 255 if the line is the /quit command, 0 on success and some other
++// error codes.
++cmd_result_t process_line(const char *line)
+ {
+-  const char *p1, *p2;
+-  char *word;
+-  const gchar *value;
+-  char *newline = (char*)line;
+-
+-  // Ignore leading COMMAND_CHAR
+-  for (p1 = line ; *p1 == COMMAND_CHAR ; p1++)
+-    ;
+-  // Locate the end of the word
+-  for (p2 = p1 ; *p2 && (*p2 != ' ') ; p2++)
+-    ;
+-  // Extract the word and look for an alias in the list
+-  word = g_strndup(p1, p2-p1);
+-  value = settings_get(SETTINGS_TYPE_ALIAS, (const char*)word);
+-  g_free(word);
+-
+-  if (value)
+-    newline = g_strdup_printf("%c%s%s", COMMAND_CHAR, value, p2);
+-
+-  return newline;
+-}
+-
+-//  cmd_get
+-// Finds command in the command list structure.
+-// Returns a pointer to the cmd entry, or NULL if command not found.
+-cmd *cmd_get(const char *command)
+-{
+-  const char *p1, *p2;
+-  char *com;
+-  GSList *sl_com;
+-
+-  // Ignore leading COMMAND_CHAR
+-  for (p1 = command ; *p1 == COMMAND_CHAR ; p1++)
+-    ;
+-  // Locate the end of the command
+-  for (p2 = p1 ; *p2 && (*p2 != ' ') ; p2++)
+-    ;
+-  // Copy the clean command
+-  com = g_strndup(p1, p2-p1);
+-
+-  // Look for command in the list
+-  for (sl_com=Commands; sl_com; sl_com = g_slist_next(sl_com)) {
+-    if (!strcasecmp(com, ((cmd*)sl_com->data)->name))
+-      break;
+-  }
+-  g_free(com);
+-
+-  if (sl_com)       // Command has been found.
+-    return (cmd*)sl_com->data;
+-  return NULL;
+-}
+-
+-//  process_command(line, iscmd)
+-// Process a command line.
+-// If iscmd is TRUE, process the command even if verbatim mmode is set;
+-// it is intended to be used for key bindings.
+-// Return 255 if this is the /quit command, and 0 for the other commands.
+-int process_command(const char *line, guint iscmd)
+-{
+-  char *p;
+-  char *xpline;
+-  cmd *curcmd;
+-
+-  if (!line)
+-    return 0;
+-
+-  // We do alias expansion here
+-  if (iscmd || scr_get_multimode() != 2)
+-    xpline = expandalias(line);
+-  else
+-    xpline = (char*)line; // No expansion in verbatim multi-line mode
+-
+-  // We want to use a copy
+-  if (xpline == line)
+-    xpline = g_strdup(line);
+-
+-  // Remove trailing spaces:
+-  for (p=xpline ; *p ; p++)
+-    ;
+-  for (p-- ; p>xpline && (*p == ' ') ; p--)
+-    *p = 0;
+-
+-  // Command "quit"?
+-  if ((iscmd || scr_get_multimode() != 2)
+-      && (!strncasecmp(xpline, mkcmdstr("quit"), strlen(mkcmdstr("quit"))))) {
+-    if (!xpline[5] || xpline[5] == ' ') {
+-      g_free(xpline);
+-      return 255;
+-    }
+-  } else if (iscmd && !strncasecmp(xpline, "quit", 4) &&
+-             (!xpline[4] || xpline[4] == ' ')) {
+-    // If iscmd is true we can have the command without the command prefix
+-    // character (usually '/').
+-    g_free(xpline);
+-    return 255;
+-  }
+-
+-  // If verbatim multi-line mode, we check if another /msay command is typed
+-  if (!iscmd && scr_get_multimode() == 2
+-      && (strncasecmp(xpline, mkcmdstr("msay "), strlen(mkcmdstr("msay "))))) {
+-    // It isn't an /msay command
 -    scr_append_multiline(xpline);
-+    gchar *utf8 = to_utf8(xpline);
-+    scr_append_multiline(utf8);
-+    g_free(utf8);
-     g_free(xpline);
-     return 0;
+-    g_free(xpline);
+-    return 0;
+-  }
+-
+-  // Commands handling
+-  curcmd = cmd_get(xpline);
+-
+-  if (!curcmd) {
+-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized command.  "
+-                 "Please see the manual for a list of known commands.");
+-    g_free(xpline);
+-    return 0;
+-  }
+-  if (!curcmd->func) {
+-    scr_LogPrint(LPRINT_NORMAL,
+-                 "This functionality is not yet implemented, sorry.");
+-    g_free(xpline);
+-    return 0;
+-  }
+-  // Lets go to the command parameters
+-  for (p = xpline+1; *p && (*p != ' ') ; p++)
+-    ;
+-  // Skip spaces
+-  while (*p && (*p == ' '))
+-    p++;
+-  // Call command-specific function
+-  retval_for_cmds = 0;
+-#ifdef MODULES_ENABLE
+-  if (curcmd->userdata)
+-    (*(void (*)(char *p, gpointer u))curcmd->func)(p, curcmd->userdata);
+-  else
+-    (*curcmd->func)(p);
+-#else
+-  (*curcmd->func)(p);
+-#endif
+-  g_free(xpline);
+-  return retval_for_cmds;
+-}
+-
+-//  process_line(line)
+-// Process a command/message line.
+-// If this isn't a command, this is a message and it is sent to the
+-// currently selected buddy.
+-// Return 255 if the line is the /quit command, or 0.
+-int process_line(const char *line)
+-{
++  gchar *utf8;
++  int retval;
++
+   if (!*line) { // User only pressed enter
+     if (scr_get_multimode()) {
+       scr_append_multiline("");
+@@ -556,75 +689,114 @@
+     }
+     if (current_buddy) {
+       if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_GROUP)
+-        do_group("toggle");
++        group_cmd (BUDDATA(current_buddy), scmd_group_toggle);
+       else {
+         // Enter chat mode
+         scr_set_chatmode(TRUE);
+         scr_show_buddy_window();
+       }
+     }
+-    return 0;
++    return cmd_result_input;
    }
-@@ -568,10 +570,12 @@
  
-   if (*line != COMMAND_CHAR) {
-     // This isn't a command
-+    gchar *utf8 = to_utf8(line);
+-  if (*line != COMMAND_CHAR) {
+-    // This isn't a command
++  utf8 = to_utf8(line);
++  if (*line != COMMAND_CHAR) { // input line
      if (scr_get_multimode())
 -      scr_append_multiline(line);
 +      scr_append_multiline(utf8);
      else
 -      say_cmd((char*)line, 0);
+-    return 0;
+-  }
+-
+-  /* It is _probably_ a command -- except for verbatim multi-line mode */
+-  return process_command(line, FALSE);
+-}
+-
+-// Helper routine for buffer item_{lock,unlock,toggle_lock}
+-// "lock" values: 1=lock 0=unlock -1=invert
+-static void roster_buddylock(char *bjid, int lock)
+-{
+-  gpointer bud = NULL;
+-  bool may_need_refresh = FALSE;
+-
+-  // Allow special jid "" or "." (current buddy)
+-  if (bjid && (!*bjid || !strcmp(bjid, ".")))
+-    bjid = NULL;
+-
+-  if (bjid) {
+-    // The JID has been specified.  Quick check...
+-    if (check_jid_syntax(bjid)) {
+-      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
+-                   "<%s> is not a valid Jabber ID.", bjid);
+-    } else {
+-      // Find the buddy
+-      GSList *roster_elt;
+-      roster_elt = roster_find(bjid, jidsearch,
+-                               ROSTER_TYPE_USER|ROSTER_TYPE_ROOM);
+-      if (roster_elt)
+-        bud = roster_elt->data;
+-      else
+-        scr_LogPrint(LPRINT_NORMAL, "This jid isn't in the roster.");
+-      may_need_refresh = TRUE;
+-    }
+-  } else {
+-    // Use the current buddy
+-    if (current_buddy)
+-      bud = BUDDATA(current_buddy);
+-  }
+-
+-  // Update the ROSTER_FLAG_USRLOCK flag
+-  if (bud) {
+-    if (lock == -1)
+-      lock = !(buddy_getflags(bud) & ROSTER_FLAG_USRLOCK);
+-    buddy_setflags(bud, ROSTER_FLAG_USRLOCK, lock);
+-    if (may_need_refresh) {
+-      buddylist_build();
+-      update_roster = TRUE;
 +      say_cmd(utf8, msgtype_not_set);
-+    g_free(utf8);
-     return 0;
++    retval = 0;
++  } else { // command or verbatim multiline message
++    retval = cmd_execute_internal (utf8, cmdexe_check_verbatim);
++    if (retval == cmd_result_verbatim) {
++      g_free(utf8); // buffer has been corrupted by parser
++      utf8 = to_utf8(line);
++      scr_append_multiline(utf8);
++      retval = cmd_result_input;
+     }
    }
++  g_free(utf8);
++
++  return retval;
+ }
  
-@@ -755,7 +759,7 @@
+-static void roster_resourcelock(char *jidres, gboolean lock) {
++//
++//  Standard types
++//
++// This section contains standard argument type definitions, used in built-in
++// mcabber commands.
++//
++
++// TODO: (and variations with 'required')
++// * cmdarg_type_roster_bjid  - in roster, with specified types -> bud
++// * cmdarg_type_roster_fjid  - in roster, with specified types -> bud + resource
++// * cmdarg_type_bjid         - any bjid -> bjid
++// * cmdarg_type_fjid         - any fjid -> fjid
++// * cmdarg_type_statusmask
++// * cmdarg_type_uint
++// * cmdarg_type_nonspace     - space only -> null
++// * cmdarg_type_bjidmask
++// * cmdarg_type_color
++// * cmdarg_type_string2enum
++// * cmdarg_type_nick        - provide completions first from current room, then from all other, nonspace, do not restrict
++
++// Uses chkdata as guint with allowed ROSTER_TYPE_*.
++// Returns buddy roster entry in value.bud.
++// Recognizes as current ".", but not "" or NULL - use defvalue.
++// Does not require freeing.
++gchar *cmdarg_check_roster_bjid_required(optarg_t *arg)
++{
++  const char *bjid;
++  guint      types;
++  gchar      *error;
++
++  if ((error = cmdarg_check_nospace_required(arg)))
++    return error;
++
++  bjid  = arg -> value.arg;
++  types = (guint) arg -> chkdata;
++
++  if (strcmp(bjid, ".")) { // jid specified
++    GSList *found;
++    if (check_jid_syntax(bjid))
++      return g_strdup_printf("<%s> is not a valid Jabber ID.", bjid);
++    // find the buddy
++    found = roster_find(bjid, jidsearch, types);
++    if (found)
++      arg -> value.bud = found->data;
++    else
++      return g_strdup_printf("Jid <%s> is not in the roster.", bjid);
++  } else { // current buddy
++    if (!current_buddy)
++      return g_strdup_printf("No jid specified, and no buddy selected.");
++    else if (buddy_gettype(BUDDATA(current_buddy)) & types)
++      arg -> value.bud = BUDDATA(current_buddy);
++    else // TODO: improve message
++      return g_strdup_printf("Currently selected buddy is of wrong type.");
++  }
++
++  return NULL;
++}
++
++// The same as bjid_required, but in the case of error just sets value.bud
++// to NULL.
++gchar *cmdarg_check_roster_bjid(optarg_t *arg)
++{
++  gchar *error = cmdarg_check_roster_bjid_required(arg);
++
++  if (error) {
++    g_free (error);
++    arg -> value.bud = NULL;
++  }
++
++  return NULL;
++}
++
++// FIXME check fjid
++// XXX can't specify resource in bud - fjid or bud+resource?
++// resource must be present in roster - cmdarg_check_fjid_in_roster?
++gchac *cmdarg_check_fjid(optarg_t *arg)
++{
+   gpointer bud = NULL;
+   char *resource = NULL;
+ 
+@@ -664,33 +836,61 @@
+       resource = jidres;
+     }
+   }
+-  
+-  if (bud && buddy_gettype(bud) & (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT)) {
+-    if (lock) {
+-      GSList *resources, *p_res;
+-      gboolean found = FALSE;
+-      resources = buddy_getresources(bud);
+-      for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) {
+-        if (!g_strcmp0((char*)p_res->data, resource))
+-          found = TRUE;
+-        g_free(p_res->data);
+-      }
+-      g_slist_free(resources);
+-      if (!found) {
+-        scr_LogPrint(LPRINT_NORMAL, "No such resource <%s>...", jidres);
+-        return;
+-      }
+-    } else {
+-      resource = NULL;
+-    }
+-    buddy_setactiveresource(bud, resource);
+-    scr_update_chat_status(TRUE);
++
++  GSList *resources, *p_res;
++  gboolean found = FALSE;
++  resources = buddy_getresources(bud);
++  for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) {
++    if (!g_strcmp0((char*)p_res->data, resource))
++      found = TRUE;
++    g_free(p_res->data);
++  }
++  g_slist_free(resources);
++  if (!found) {
++    scr_LogPrint(LPRINT_NORMAL, "No such resource <%s>...", jidres);
++    return;
+   }
+ }
++
++//
++//  Command definitions
++//
++// This section contains definitions for built-in mcabber commands.
++// If you add a definition here, that should be automatically loaded,
++// you also have to add it to cmd_init().
++//
++
++//
++//  /roster
++//
++
++// Helper routine for /roster item_{lock,unlock,toggle_lock}
++// "lock" values: 1=lock 0=unlock -1=invert
++static void roster_buddylock(gpointer bud, int lock)
++{
++  // Update the ROSTER_FLAG_USRLOCK flag
++  if (lock == -1)
++    lock = !(buddy_getflags(bud) & ROSTER_FLAG_USRLOCK);
++  buddy_setflags(bud, ROSTER_FLAG_USRLOCK, lock);
++  if ((!current_buddy) || bud == BUDDATA(current_buddy)) {
++    buddylist_build();
++    update_roster = TRUE;
++  }
++}
++
++static void roster_resourcelock(gpointer bud, const char *resource, gboolean lock)
++{
++  if (lock)
++    buddy_setactiveresource(bud, resource);
++  else
++    buddy_setactiveresource(buf, NULL);
++  scr_update_chat_status(TRUE);
++}
++
+ //  display_and_free_note(note, winId)
+ // Display the note information in the winId buffer, and free note
+ // (winId is a bare jid or NULL for the status window, in which case we
+-// display the note jid too)
++// display the note jid too).
+ static void display_and_free_note(struct annotation *note, const char *winId)
+ {
+   gchar tbuf[128];
+@@ -755,41 +955,15 @@
    g_slist_free(notes);
  }
  
 -static void roster_note(char *arg)
-+static void roster_note(gchar *arg)
++static void roster_note(gpointer bud, gboolean reset, gchar *note)
  {
-   const char *bjid;
-   guint type;
-@@ -781,14 +785,9 @@
-   }
- 
-   if (arg && *arg) {  // Set a note
+-  const char *bjid;
+-  guint type;
+-
+-  if (!current_buddy)
+-    return;
+-
+-  bjid = buddy_getjid(BUDDATA(current_buddy));
+-  type = buddy_gettype(BUDDATA(current_buddy));
+-
+-  if (!bjid && type == ROSTER_TYPE_SPECIAL && !arg) {
+-    // We're in the status window (the only special buffer currently)
+-    // Let's display all server notes
+-    display_all_annotations();
+-    return;
+-  }
+-
+-  if (!bjid || (type != ROSTER_TYPE_USER &&
+-               type != ROSTER_TYPE_ROOM &&
+-               type != ROSTER_TYPE_AGENT)) {
+-    scr_LogPrint(LPRINT_NORMAL, "This item can't have a note.");
+-    return;
+-  }
+-
+-  if (arg && *arg) {  // Set a note
 -    gchar *msg, *notetxt;
 -    msg = to_utf8(arg);
 -    if (!strcmp(msg, "-"))
@@ -466,19 +1860,105 @@
 -      notetxt = msg;
 -    xmpp_set_storage_rosternotes(bjid, notetxt);
 -    g_free(msg);
-+    if (!strcmp(arg, "-"))
-+      arg = NULL; // delete note
-+    xmpp_set_storage_rosternotes(bjid, arg);
-   } else {      // Display a note
+-  } else {      // Display a note
++  const char *bjid = buddy_getjid(bud);
++
++  if (note) // set note
++    xmpp_set_storage_rosternotes(bjid, note);
++  else if (reset) // delete note
++    xmpp_set_storage_rosternotes(bjid, NULL);
++  else { // display a note
      struct annotation *note = xmpp_get_storage_rosternotes(bjid, FALSE);
      if (note) {
-@@ -819,175 +818,228 @@
- /* All these do_*() functions will be called with a "arg" parameter */
- /* (with arg not null)                                              */
+       display_and_free_note(note, bjid);
+@@ -800,194 +974,244 @@
+   }
+ }
  
+-//  roster_updown(updown, nitems)
+-// updown: -1=up, +1=down
+-inline static void roster_updown(int updown, char *nitems)
++typedef enum {
++  scmd_roster_bottom, scmd_roster_top, scmd_roster_up, scmd_roster_down,
++  scmd_roster_group_prev, scmd_roster_group_next,
++  scmd_roster_alternate,
++  scmd_roster_unread_first, scmd_roster_unread_next,
++  scmd_roster_search,
++  scmd_roster_display,
++  scmd_roster_hide_offline, scmd_roster_show_offline, scmd_roster_toggle_offline,
++  scmd_roster_item_lock, scmd_roster_item_unlock, scmd_roster_item_toggle_lock,
++  scmd_roster_note, scmd_roster_notes,
++  scmd_roster_resource_lock, scmd_roster_resource_unlock,
++  scmd_roster_hide, scmd_roster_show, scmd_roster_toggle,
++} scmd_roster_t;
++
++#define SCMD_ROSTER(NAME, ARGS) \
++    { #NAME, NULL, NULL, NULL, ARGS, NULL, (gpointer)scmd_roster_##NAME }
++static cmdopts_t def_roster = {
++  "roster",
++  NULL,
++  do_roster,
++  NULL,
++  {{cmdarg_subcmd | cmdarg_check, NULL, NULL}, NULL},
++  {
++    SCMD_ROSTER(bottom, NULL),
++    SCMD_ROSTER(top,    NULL),
++    SCMD_ROSTER(up,   {{cmdarg_check, "1", cmdarg_type_uint}, NULL}),
++    SCMD_ROSTER(down, {{cmdarg_check, "1", cmdarg_type_uint}, NULL}),
++    SCMD_ROSTER(group_prev, NULL),
++    SCMD_ROSTER(group_next, NULL),
++    SCMD_ROSTER(alternate, NULL),
++    SCMD_ROSTER(unread_first, NULL),
++    SCMD_ROSTER(unread_next,  NULL),
++    SCMD_ROSTER(search, {{cmdarg_catchall|cmdarg_plain|cmdarg_check, NULL, cmdarg_type_nonspace}, NULL}),
++    SCMD_ROSTER(display, {{cmdarg_check, NULL, cmdarg_type_statusmask}, NULL}),
++    SCMD_ROSTER(hide_offline,   NULL),
++    SCMD_ROSTER(show_offline,   NULL),
++    SCMD_ROSTER(toggle_offline, NULL),
++    SCMD_ROSTER(item_lock,        {{cmdarg_check, ".", cmdarg_type_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_ROOM)}, NULL}),
++    SCMD_ROSTER(item_unlock,      {{cmdarg_check, ".", cmdarg_type_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_ROOM)}, NULL}),
++    SCMD_ROSTER(item_toggle_lock, {{cmdarg_check, ".", cmdarg_type_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_ROOM)}, NULL}),
++    { "note", NULL, NULL,
++      {
++        {cmdopt_switch,  'r', "reset", {cmdarg_default, NULL, NULL, NULL}},
++        {cmdopt_default, 'j', "jid",   {cmdarg_check, ".", cmdarg_type_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT)}},
++        NULL,
++      },
++      {
++        {cmdarg_catchall|cmdarg_plain, NULL, cmdarg_type_nonspace},
++        NULL,
++      },
++      NULL, (gpointer)scmd_roster_note
++    },
++    SCMD_ROSTER(notes, NULL),
++    SCMD_ROSTER(resource_lock,   {{cmdarg_check, NULL, cmdarg_type_fjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_AGENT}, NULL}),
++    SCMD_ROSTER(resource_unlock, {{cmdarg_check, NULL, cmdarg_type_fjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_AGENT}, NULL}),
++    SCMD_ROSTER(hide,   NULL),
++    SCMD_ROSTER(show,   NULL),
++    SCMD_ROSTER(toggle, NULL),
++    NULL,
++  },
++};
++
++static gchar *do_roster(cmdopts_t *options)
+ {
+-  int nbitems;
+-
+-  if (!nitems || !*nitems)
+-    nbitems = 1;
+-  else
+-    nbitems = strtol(nitems, NULL, 10);
+-
+-  if (nbitems > 0)
+-    scr_roster_up_down(updown, nbitems);
+-}
+-
+-/* Commands callback functions */
+-/* All these do_*() functions will be called with a "arg" parameter */
+-/* (with arg not null)                                              */
+-
 -static void do_roster(char *arg)
-+static void do_roster(char *args)
- {
+-{
 -  char **paramlst;
 -  char *subcmd;
 -
@@ -489,95 +1969,40 @@
 -  if (!subcmd || !*subcmd) {
 -    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
 -    free_arg_lst(paramlst);
-+  enum roster_scmd_t {
-+    roster_scmd_bottom, roster_scmd_top, roster_scmd_up, roster_scmd_down,
-+    roster_scmd_group_prev, roster_scmd_group_next,
-+    roster_scmd_alternate,
-+    roster_scmd_unread_first, roster_scmd_unread_next,
-+    roster_scmd_search,
-+    roster_scmd_display,
-+    roster_scmd_hide_offline, roster_scmd_show_offline, roster_scmd_toggle_offline,
-+    roster_scmd_item_lock, roster_scmd_item_unlock, roster_scmd_item_toggle_lock,
-+    roster_scmd_note,
-+    roster_scmd_resource_lock, roster_scmd_resource_unlock,
-+    roster_scmd_hide, roster_scmd_show, roster_scmd_toggle,
-+  } subcmd;
-+#define ROSTER_SCMD_NOARG(NAME) \
-+      { #NAME, NULL, NULL, NULL, (gpointer)roster_scmd_##NAME, 0 }
-+// all of them have at most one argument
-+#define ROSTER_SCMD(NAME, FLAGS, VALUE) \
-+      { #NAME, NULL, \
-+        (cmdarg_t[1]){{ CMDOPT_LAST | FLAGS, { .arg = VALUE } }}, \
-+        NULL, (gpointer)roster_scmd_##NAME, 0 }
-+  cmdopts_t options = {
-+    "roster",
-+    NULL,
-+    (cmdarg_t[1]){
-+      { CMDOPT_REQUIRED | CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } }
-+    },
-+    (cmdopts_t[23]){
-+      ROSTER_SCMD_NOARG(bottom),
-+      ROSTER_SCMD_NOARG(top),
-+      ROSTER_SCMD(up,               0, "1"), // num lines
-+      ROSTER_SCMD(down,             0, "1"), // num lines
-+      ROSTER_SCMD_NOARG(group_prev),
-+      ROSTER_SCMD_NOARG(group_next),
-+      ROSTER_SCMD_NOARG(alternate),
-+      ROSTER_SCMD_NOARG(unread_first),
-+      ROSTER_SCMD_NOARG(unread_next),
-+      ROSTER_SCMD(search, CMDOPT_REQUIRED | CMDOPT_CATCHALL, NULL), // to find
-+      ROSTER_SCMD(display,          0, NULL), // status mask
-+      ROSTER_SCMD_NOARG(hide_offline),
-+      ROSTER_SCMD_NOARG(show_offline),
-+      ROSTER_SCMD_NOARG(toggle_offline),
-+      ROSTER_SCMD(item_lock,        0, "."), // jid
-+      ROSTER_SCMD(item_unlock,      0, "."), // jid
-+      ROSTER_SCMD(item_toggle_lock, 0, "."), // jid
-+      ROSTER_SCMD(note, CMDOPT_CATCHALL, NULL), // note
-+      ROSTER_SCMD(resource_lock,    0, NULL), // resource/jid
-+      ROSTER_SCMD(resource_unlock,  0, NULL), // resource/jid
-+      ROSTER_SCMD_NOARG(hide),
-+      ROSTER_SCMD_NOARG(show),
-+      { "toggle", NULL, NULL, NULL, (gpointer)roster_scmd_toggle,
-+        CMDOPT_LAST },
-+    },
-+    NULL,
-+  };
-+  gchar *arg;
+-    return;
+-  }
+-
+-  if (!strcasecmp(subcmd, "top")) {
++  scmd_roster_t subcmd = (scmd_roster_t) (options -> args[0] -> value.cmd -> userdata);
++  cmdarg_t      *arg   = NULL;
 +
-+  if (cmdopts_parse(args, &options))
-     return;
++  if (options -> args[0] -> value.cmd -> args)
++    arg = options -> args[0] -> value.cmd -> args[0];
 +
-+  subcmd = (enum roster_scmd_t) (options.args[0].value.cmd -> userdata);
-+  if (options.args[0].value.cmd -> args != NULL) {
-+    arg = options.args[0].value.cmd -> args[0].value.arg;
-   }
- 
--  if (!strcasecmp(subcmd, "top")) {
-+  if (subcmd == roster_scmd_bottom) {
++  if (subcmd == scmd_roster_bottom) {
 +    scr_roster_bottom();
 +    update_roster = TRUE;
-+  } else if (subcmd == roster_scmd_top) {
++  } else if (subcmd == scmd_roster_top) {
      scr_roster_top();
      update_roster = TRUE;
 -  } else if (!strcasecmp(subcmd, "bottom")) {
 -    scr_roster_bottom();
-+  } else if (subcmd == roster_scmd_up) {
-+    roster_updown(-1, arg);
-+  } else if (subcmd == roster_scmd_down) {
-+    roster_updown(1, arg);
-+  } else if (subcmd == roster_scmd_group_prev) {
++  } else if (subcmd == scmd_roster_up) {
++    scr_roster_up_down(-1, arg -> value.uint);
++  } else if (subcmd == scmd_roster_down) {
++    scr_roster_up_down(1, arg -> value.uint);
++  } else if (subcmd == scmd_roster_group_prev) {
 +    scr_roster_prev_group();
-+  } else if (subcmd == roster_scmd_group_next) {
++  } else if (subcmd == scmd_roster_group_next) {
 +    scr_roster_next_group();
-+  } else if (subcmd == roster_scmd_alternate) {
++  } else if (subcmd == scmd_roster_alternate) {
 +    scr_roster_jump_alternate();
-+  } else if (subcmd == roster_scmd_unread_first) {
++  } else if (subcmd == scmd_roster_unread_first) {
 +    scr_roster_unread_message(0);
-+  } else if (subcmd == roster_scmd_unread_next) {
++  } else if (subcmd == scmd_roster_unread_next) {
 +    scr_roster_unread_message(1);
-+  } else if (subcmd == roster_scmd_search) {
-+    scr_roster_search(arg);
++  } else if (subcmd == scmd_roster_search) {
++    scr_roster_search(arg -> value.arg);
      update_roster = TRUE;
 -  } else if (!strcasecmp(subcmd, "hide")) {
 -    scr_roster_visibility(0);
@@ -586,35 +2011,32 @@
 -  } else if (!strcasecmp(subcmd, "toggle")) {
 -    scr_roster_visibility(-1);
 -  } else if (!strcasecmp(subcmd, "hide_offline")) {
-+  } else if (subcmd == roster_scmd_display) {
-+    scr_roster_display(arg);
-+  } else if (subcmd == roster_scmd_hide_offline) {
++  } else if (subcmd == scmd_roster_display) {
++    scr_roster_display(arg -> value.arg);
++  } else if (subcmd == scmd_roster_hide_offline) {
      buddylist_set_hide_offline_buddies(TRUE);
 -    if (current_buddy)
 +    if (current_buddy) // XXX
        buddylist_build();
      update_roster = TRUE;
 -  } else if (!strcasecmp(subcmd, "show_offline")) {
-+  } else if (subcmd == roster_scmd_show_offline) {
++  } else if (subcmd == scmd_roster_show_offline) {
      buddylist_set_hide_offline_buddies(FALSE);
      buddylist_build();
      update_roster = TRUE;
 -  } else if (!strcasecmp(subcmd, "toggle_offline")) {
-+  } else if (subcmd == roster_scmd_toggle_offline) {
++  } else if (subcmd == scmd_roster_toggle_offline) {
      buddylist_set_hide_offline_buddies(-1);
      buddylist_build();
      update_roster = TRUE;
 -  } else if (!strcasecmp(subcmd, "display")) {
 -    scr_roster_display(arg);
 -  } else if (!strcasecmp(subcmd, "item_lock")) {
-+  } else if (subcmd == roster_scmd_item_lock) {
-     roster_buddylock(arg, 1);
+-    roster_buddylock(arg, 1);
 -  } else if (!strcasecmp(subcmd, "item_unlock")) {
-+  } else if (subcmd == roster_scmd_item_unlock) {
-     roster_buddylock(arg, 0);
+-    roster_buddylock(arg, 0);
 -  } else if (!strcasecmp(subcmd, "item_toggle_lock")) {
-+  } else if (subcmd == roster_scmd_item_toggle_lock) {
-     roster_buddylock(arg, -1);
+-    roster_buddylock(arg, -1);
 -  } else if (!strcasecmp(subcmd, "unread_first")) {
 -    scr_roster_unread_message(0);
 -  } else if (!strcasecmp(subcmd, "unread_next")) {
@@ -639,29 +2061,92 @@
 -  } else if (!strcasecmp(subcmd, "group_next")) {
 -    scr_roster_next_group();
 -  } else if (!strcasecmp(subcmd, "note")) {
-+  } else if (subcmd == roster_scmd_note) {
-     roster_note(arg);
+-    roster_note(arg);
 -  } else if (!strcasecmp(subcmd, "resource_lock")) {
-+  } else if (subcmd == roster_scmd_resource_lock) {
-     roster_resourcelock(arg, TRUE);
+-    roster_resourcelock(arg, TRUE);
 -  } else if (!strcasecmp(subcmd, "resource_unlock")) {
-+  } else if (subcmd == roster_scmd_resource_unlock) {
-     roster_resourcelock(arg, FALSE);
+-    roster_resourcelock(arg, FALSE);
 -  } else
 -    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
 -  free_arg_lst(paramlst);
-+  } else if (subcmd == roster_scmd_hide) {
++  } else if (subcmd == scmd_roster_item_lock) {
++    roster_buddylock(arg -> value.bud, 1);
++  } else if (subcmd == scmd_roster_item_unlock) {
++    roster_buddylock(arg -> value.bud, 0);
++  } else if (subcmd == scmd_roster_item_toggle_lock) {
++    roster_buddylock(arg -> value.bud, -1);
++  } else if (subcmd == scmd_roster_note) {
++    roster_note(options -> cmds[18] -> opts[1] -> value.bud,
++                options -> cmds[18] -> opts[0] -> value.swc,
++                arg -> value.arg);
++  } else if (subcmd == scmd_roster_notes) {
++    display_all_annotations();
++  } else if (subcmd == scmd_roster_resource_lock) { // FIXME implement fjid checker and decide where to get resource
++    roster_resourcelock(arg -> value.bud, resource, TRUE);
++  } else if (subcmd == scmd_roster_resource_unlock) {
++    roster_resourcelock(arg -> value.bud, NULL, FALSE);
++  } else if (subcmd == scmd_roster_hide) {
 +    scr_roster_visibility(0);
-+  } else if (subcmd == roster_scmd_show) {
++  } else if (subcmd == scmd_roster_show) {
 +    scr_roster_visibility(1);
-+  } else { // roster_scmd_toggle
++  } else { // scmd_roster_toggle
 +    scr_roster_visibility(-1);
 +  }
 +
-+  cmdopts_free(&options);
++  return NULL;
  }
  
- void do_color(char *arg)
+-void do_color(char *arg)
++//
++//  /color
++//
++
++// TODO:
++// * cmdarg_type_color_statusmask - also accept "clear"
++// * cmdarg_type_color_roomjid    - also accept "*"
++
++typedef enum {
++  scmd_color_roster,
++  scmd_color_mucnick,
++  scmd_color_muc,
++} scmd_color_t;
++
++static string2enum_t s2e_color_muc[] = {
++  { "on",     MC_ALL    },
++  { "off",    MC_OFF    },
++  { "preset", MC_PRESET },
++  { "-",      MC_REMOVE },
++  { NULL,     0         },
++};
++
++cmdopts_t def_color = {
++  "color",
++  NULL,
++  do_color,
++  NULL,
++  {{ cmdarg_subcmd | cmdarg_check, NULL, NULL }, NULL},
++  {
++    {"roster", NULL, NULL, NULL, {
++        { cmdarg_check,   NULL, cmdarg_type_color_statusmask },
++        { cmdarg_default, NULL, cmdarg_type_bjidmask         },
++        { cmdarg_default, NULL, cmdarg_type_color            },
++        NULL,
++      }, NULL, (gpointer)scmd_color_roster},
++    {"muc", NULL, NULL, NULL, {
++        { cmdarg_check, NULL, cmdarg_type_color_roomjid },
++        { cmdarg_check, "on", cmdarg_type_string2enum, (gpointer)s2e_color_muc},
++        NULL,
++      }, NULL, (gpointer)scmd_color_muc},
++    {"mucnick", NULL, NULL, NULL {
++        { cmdarg_check, NULL, cmdarg_type_nick  },
++        { cmdarg_check, NULL, cmdarg_type_color },
++        NULL,
++      }, NULL, (gpointer)scmd_color_mucnick},
++    NULL,
++  },
++};
++
++static gchar *do_color(cmdopts_t *options)
  {
 -  char **paramlst;
 -  char *subcmd;
@@ -673,45 +2158,7 @@
 -  if (!subcmd || !*subcmd) {
 -    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
 -    free_arg_lst(paramlst);
-+  enum color_scmd_t {
-+    color_scmd_roster,
-+    color_scmd_mucnick,
-+    color_scmd_muc,
-+  } subcmd;
-+  cmdopts_t options = {
-+    "color",
-+    NULL,
-+    (cmdarg_t[1]){
-+      { CMDOPT_REQUIRED | CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
-+    },
-+    (cmdopts_t[3]){
-+      { "roster", NULL,
-+        (cmdarg_t[3]){
-+          { CMDOPT_REQUIRED, { .arg = NULL } }, // status mask or "clear"
-+          { 0,               { .arg = NULL } }, // jid mask
-+          { CMDOPT_LAST,     { .arg = NULL } }, // color
-+        },
-+        NULL, (gpointer)color_scmd_roster, 0,
-+      },
-+      { "muc", NULL,
-+        (cmdarg_t[2]){
-+          { CMDOPT_REQUIRED, { .arg = NULL } }, // jid
-+          { CMDOPT_LAST,     { .arg = "on" } }, // on/off/preset/-
-+        },
-+        NULL, (gpointer)color_scmd_muc, 0,
-+      },
-+      { "mucnick", NULL,
-+        (cmdarg_t[2]){
-+          { CMDOPT_REQUIRED,               { .arg = NULL } }, // nick
-+          { CMDOPT_REQUIRED | CMDOPT_LAST, { .arg = NULL } }, // color
-+        },
-+        NULL, (gpointer)color_scmd_mucnick, CMDOPT_LAST,
-+      },
-+    },
-+  };
-+
-+  if (cmdopts_parse(arg, &options))
-     return;
+-    return;
 -  }
 -
 -  if (!strcasecmp(subcmd, "roster")) {
@@ -723,21 +2170,21 @@
 -    color = arglist[2];
 -
 -    if (status && !strcmp(status, "clear")) { // Not a color command, clear all
-+  
-+  subcmd = (enum color_scmd_t) options.args[0].value.cmd -> userdata;
++  scmd_color_t subcmd = (scmd_color_t) options -> args[0] -> value.cmd -> userdata;
 +
-+  if (subcmd == color_scmd_roster) {
-+    const gchar *status   = options.cmds[0].args[0].value.arg;
-+    const gchar *wildcard = options.cmds[0].args[1].value.arg;
-+    const gchar *color    = options.cmds[0].args[2].value.arg;
++  if (subcmd == scmd_color_roster) {
++    const gchar *status   = options -> cmds[0] -> args[0] -> value.arg;
++    const gchar *wildcard = options -> cmds[0] -> args[1] -> value.arg;
++    const gchar *color    = options -> cmds[0] -> args[2] -> value.arg;
 +    if (!strcmp(status, "clear")) { // Not a color command, clear all
        scr_roster_clear_color();
        update_roster = TRUE;
      } else {
 -      if (!status || !*status || !wildcard || !*wildcard || !color || !*color) {
 -        scr_LogPrint(LPRINT_NORMAL, "Missing argument");
-+      if (!*status || !wildcard || !*wildcard || !color || !*color) {
-+        scr_log_print(LPRINT_NORMAL, "Missing argument");
++      if (!wildcard || !*wildcard || !color || !*color) {
++        // freaking "clear" :(
++        return g_strdup ("Missing argument.");
        } else {
          update_roster = scr_roster_color(status, wildcard, color) ||
                          update_roster;
@@ -770,28 +2217,8 @@
 -          else
 -            scr_LogPrint(LPRINT_NORMAL, "Unknown coloring mode");
 -        }
-+  } else if (subcmd == color_scmd_muc) {
-+    const gchar *muc  = options.cmds[1].args[0].value.arg;
-+    const gchar *mode = options.cmds[1].args[1].value.arg;
-+    if (!strcmp(muc, "."))
-+      if (!(muc = CURRENT_JID))
-+        scr_LogPrint(LPRINT_NORMAL, "No JID selected");
-+    if (muc) {
-+      if (check_jid_syntax(muc) && strcmp(muc, "*"))
-+        scr_LogPrint(LPRINT_NORMAL, "Not a JID");
-+      else {
-+        if (!strcasecmp(mode, "on"))
-+          scr_muc_color(muc, MC_ALL);
-+        else if (!strcasecmp(mode, "preset"))
-+          scr_muc_color(muc, MC_PRESET);
-+        else if (!strcasecmp(mode, "off"))
-+          scr_muc_color(muc, MC_OFF);
-+        else if (!strcmp(mode, "-"))
-+          scr_muc_color(muc, MC_REMOVE);
-+        else
-+          scr_LogPrint(LPRINT_NORMAL, "Unknown coloring mode");
-       }
-     }
+-      }
+-    }
 -    free_arg_lst(arglist);
 -    g_free(free_muc);
 -  } else if (!strcasecmp(subcmd, "mucnick")) {
@@ -805,16 +2232,36 @@
 -  } else
 -    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
 -  free_arg_lst(paramlst);
-+  } else { // color_scmd_mucnick
-+    const gchar *nick  = options.cmds[2].args[0].value.arg;
-+    const gchar *color = options.cmds[2].args[1].value.arg;
-+    scr_muc_nick_color(nick, color);
++  } else if (subcmd == scmd_color_muc) {
++    scr_muc_color ( options -> cmds[1] -> args[0] -> value.arg,
++                    options -> cmds[1] -> args[1] -> value.uint );
++  } else { // scmd_color_mucnick
++    scr_muc_nick_color( options -> cmds[2] -> args[0] -> value.arg,
++                        options -> cmds[2] -> args[1] -> value.arg );
 +  }
 +
-+  cmdopts_free(&options);
++  return NULL;
  }
  
 -//  cmd_setstatus(recipient, arg)
++//
++//  /status
++//
++
++static string2enum_t s2e_status[] = {
++  { "away",      away        },
++  { "offline",   offline     },
++  { "online",    available   },
++  { "avail",     available   },
++  { "notavail",  notavail    },
++  { "dnd",       dontdisturb },
++  { "free",      freeforchat },
++#ifdef WITH_DEPRECATED_STATUS_INVISIBLE
++  { "invisible", invisible   },
++#endif
++  { NULL,        0           },
++};
++
 +//  cmd_setstatus(recipient, status, message)
  // Set your Jabber status.
 -// - if recipient is not NULL, the status is sent to this contact only
@@ -829,7 +2276,7 @@
    enum imstatus st;
  
    if (!xmpp_is_online())
-@@ -1000,15 +1052,15 @@
+@@ -1000,15 +1224,15 @@
    if (!recipient)
      scr_check_auto_away(TRUE);
  
@@ -850,7 +2297,7 @@
    if      (!strcasecmp(status, IMSTATUS_OFFLINE))       st = offline;
    else if (!strcasecmp(status, IMSTATUS_ONLINE))        st = available;
    else if (!strcasecmp(status, IMSTATUS_AVAILABLE))     st = available;
-@@ -1020,64 +1072,76 @@
+@@ -1020,64 +1244,88 @@
    else if (!strcasecmp(status, IMSTATUS_NOTAVAILABLE))  st = notavail;
    else if (!strcasecmp(status, IMSTATUS_FREE4CHAT))     st = freeforchat;
    else if (!strcasecmp(status, "message")) {
@@ -882,43 +2329,51 @@
 -  free_arg_lst(paramlst);
  }
  
- static void do_status(char *arg)
+-static void do_status(char *arg)
++
++cmdopts_t def_status = {
++  "status",
++  NULL,
++  do_status,
++  NULL,
++  {
++    {cmdarg_default, NULL, cmdarg_type_status_status, (string2enum_t)s2e_status2},
++    {cmdarg_catchall|cmdarg_plain, NULL, cmdarg_type_nonspace},
++    NULL,
++  },
++  NULL,
++};
++
++static gchar *do_status(cmdopts_t *options)
  {
 -  if (!*arg) {
-+  cmdopts_t options = {
-+    "status",
-+    NULL,
-+    (cmdarg_t[2]){
-+      // status
-+      { 0,                                            { .arg = NULL } },
-+      // message
-+      { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
-+    },
-+    NULL,
-+  };
-+
-+  if (cmdopts_parse(arg, &options))
-+    return;
-+
 +  if (options.args[0].value.arg == NULL) {
      const char *sm = xmpp_getstatusmsg();
      scr_LogPrint(LPRINT_NORMAL, "Your status is: [%c] %s",
                   imstatus2char[xmpp_getstatus()],
                   (sm ? sm : ""));
--    return;
 +  } else {
 +    cmd_setstatus(NULL, options.args[0].value.arg, options.args[1].value.arg);
-   }
--  arg = to_utf8(arg);
--  cmd_setstatus(NULL, arg);
--  g_free(arg);
-+  
-+  cmdopts_free(&options);
- }
- 
- static void do_status_to(char *arg)
- {
--  char **paramlst;
++  }
++  return NULL;
++}
++
++cmdopts_t def_status_to = {
++  "status_to",
++  NULL,
++  do_status_to,
++  NULL,
++  { // FIXME NOT in roster, just any fjid, and as a string
++    {cmdarg_check, NULL, cmdarg_type_fjid},
++    {cmdarg_check, NULL, cmdarg_type_status_status, (string2enum_t)s2e_status2},
++    {cmdarg_catchall|cmdarg_plain, NULL, cmdarg_type_nonspace},
++    NULL,
++  },
++  NULL,
++};
++
++static gchar *do_status_to(cmdopts_t *options)
++{
 +  cmdopts_t options = {
 +    "status_to",
 +    NULL,
@@ -932,7 +2387,20 @@
 +    },
 +    NULL,
 +  };
-   char *fjid, *st, *msg;
++  char *fjid, *st, *msg;
++
++  if (cmdopts_parse(arg, &options))
+     return;
+-  }
+-  arg = to_utf8(arg);
+-  cmd_setstatus(NULL, arg);
+-  g_free(arg);
+-}
+-
+-static void do_status_to(char *arg)
+-{
+-  char **paramlst;
+-  char *fjid, *st, *msg;
 -  char *jid_utf8 = NULL;
 -
 -  paramlst = split_arg(arg, 3, 1); // jid, status, [message]
@@ -944,9 +2412,7 @@
 -    scr_LogPrint(LPRINT_NORMAL,
 -                 "Please specify both a Jabber ID and a status.");
 -    free_arg_lst(paramlst);
-+
-+  if (cmdopts_parse(arg, &options))
-     return;
+-    return;
 -  }
 +
 +  fjid = options.args[0].value.arg;
@@ -955,7 +2421,7 @@
  
    // Allow things like /status_to "" away
    if (!*fjid || !strcmp(fjid, "."))
-@@ -1086,15 +1150,13 @@
+@@ -1086,15 +1334,13 @@
    if (fjid) {
      // The JID has been specified.  Quick check...
      if (check_jid_syntax(fjid)) {
@@ -972,7 +2438,7 @@
      }
    } else {
      // Add the current buddy
-@@ -1105,144 +1167,190 @@
+@@ -1105,144 +1351,208 @@
    }
  
    if (fjid) {
@@ -995,6 +2461,7 @@
 +  cmdopts_free(&options);
  }
  
++#if 0
  static void do_add(char *arg)
  {
 -  char **paramlst;
@@ -1164,13 +2631,30 @@
 +  cmdopts_free(&options);
 +}
 +
++#endif
++
++static void group_cmd (gpointer group, group_scmd_t action) 
++{
++  // We'll have to redraw the chat window if we're not currently on the group
++  // entry itself, because it means we'll have to leave the current buddy
++  // chat window.
++  if (action != scmd_group_unfold &&
++      ((!current_buddy) ||
++       (group != BUDDATA(current_buddy) &&
++        group == buddy_getgroup(BUDDATA(current_buddy)))))
++    scr_roster_prev_group();
++
++  buddy_hide_group(group, action);
++
++  buddylist_build();
++  update_roster = TRUE;
++}
++
++#if 0
++
 +static void do_group(char *arg)
 +{
-+  enum group_scmd_t {
-+    group_scmd_unfold = 0,
-+    group_scmd_fold   = 1,
-+    group_scmd_toggle = -1,
-+  } subcmd;
++  scmd_group_t subcmd;
 +  cmdopts_t options = {
 +    "group",
 +    NULL,
@@ -1266,19 +2750,29 @@
      if (roster_elt)
        group = buddy_getgroup(roster_elt->data);
    } else {
-@@ -1264,20 +1372,20 @@
+@@ -1253,31 +1563,19 @@
+     goto do_group_return;
+   }
+ 
+-  // We'll have to redraw the chat window if we're not currently on the group
+-  // entry itself, because it means we'll have to leave the current buddy
+-  // chat window.
+-  leave_buddywindow = (group != BUDDATA(current_buddy) &&
+-                       group == buddy_getgroup(BUDDATA(current_buddy)));
+-
+   if (!(buddy_gettype(group) & ROSTER_TYPE_GROUP)) {
+     scr_LogPrint(LPRINT_NORMAL, "You need to select a group.");
      goto do_group_return;
    }
  
 -  if (group_state != group_unfold && leave_buddywindow)
-+  if (subcmd != group_scmd_unfold && leave_buddywindow)
-     scr_roster_prev_group();
- 
+-    scr_roster_prev_group();
+-
 -  buddy_hide_group(group, group_state);
-+  buddy_hide_group(group, subcmd);
- 
-   buddylist_build();
-   update_roster = TRUE;
+-
+-  buddylist_build();
+-  update_roster = TRUE;
++  group_cmd (group, subcmd);
  
  do_group_return:
 -  free_arg_lst(paramlst);
@@ -1291,7 +2785,7 @@
  {
    char *bare_jid, *rp;
    char *hmsg;
-@@ -1285,6 +1393,7 @@
+@@ -1285,6 +1583,7 @@
    gint retval = 0;
    int isroom;
    gpointer xep184 = NULL;
@@ -1299,7 +2793,7 @@
  
    if (!xmpp_is_online()) {
      scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
-@@ -1299,11 +1408,15 @@
+@@ -1299,11 +1598,15 @@
      return 1;
    }
    if (check_jid_syntax((char*)fjid)) {
@@ -1317,7 +2811,7 @@
    // We must use the bare jid in hk_message_out()
    rp = strchr(fjid, JID_RESOURCE_SEPARATOR);
    if (rp)
-@@ -1354,8 +1467,7 @@
+@@ -1354,8 +1657,7 @@
  //  send_message(msg, subj, type_overwrite)
  // Write the message in the buddy's window and send the message on
  // the network.
@@ -1327,7 +2821,7 @@
  {
    const char *bjid;
    char *jid;
-@@ -1378,34 +1490,13 @@
+@@ -1378,34 +1680,13 @@
    else
      jid = g_strdup(bjid);
  
@@ -1364,7 +2858,7 @@
  
    scr_set_chatmode(TRUE);
    scr_show_buddy_window();
-@@ -1424,80 +1515,131 @@
+@@ -1424,80 +1705,131 @@
    }
  
    buddy_setflags(bud, ROSTER_FLAG_LOCK, TRUE);
@@ -1545,7 +3039,7 @@
  
    if (!scr_get_multimode()) {
      scr_LogPrint(LPRINT_NORMAL, "No message to send.  "
-@@ -1508,49 +1650,47 @@
+@@ -1508,49 +1840,47 @@
    scr_set_chatmode(TRUE);
    scr_show_buddy_window();
  
@@ -1631,7 +3125,7 @@
  }
  
  //  load_message_from_file(filename)
-@@ -1566,7 +1706,7 @@
+@@ -1566,7 +1896,7 @@
    char *next_utf8_char;
    size_t len;
  
@@ -1640,7 +3134,7 @@
  
    if (!fd || fstat(fileno(fd), &buf)) {
      scr_LogPrint(LPRINT_LOGNORM, "Cannot open message file (%s)", filename);
-@@ -1634,130 +1774,103 @@
+@@ -1634,130 +1964,103 @@
  
  static void do_say_to(char *arg)
  {
@@ -1827,7 +3321,7 @@
  }
  
  //  buffer_updown(updown, nblines)
-@@ -1775,27 +1888,10 @@
+@@ -1775,27 +2078,10 @@
      scr_buffer_scroll_up_down(updown, nblines);
  }
  
@@ -1855,7 +3349,7 @@
    t = from_iso8601(date, 0);
    if (t)
      scr_buffer_date(t);
-@@ -1804,98 +1900,156 @@
+@@ -1804,98 +2090,156 @@
                   "not correctly formatted or invalid.");
  }
  
@@ -2094,7 +3588,7 @@
  }
  
  static void do_info(char *arg)
-@@ -2033,29 +2187,20 @@
+@@ -2033,29 +2377,20 @@
    }
  }
  
@@ -2133,7 +3627,7 @@
  
    // Enter chat mode
    scr_set_chatmode(TRUE);
-@@ -2075,12 +2220,12 @@
+@@ -2075,12 +2410,12 @@
      rstatus = buddy_getstatus(bud, p_res->data);
      rst_msg = buddy_getstatusmsg(bud, p_res->data);
  
@@ -2148,7 +3642,7 @@
          enum imrole role = buddy_getrole(bud, p_res->data);
          enum imaffiliation affil = buddy_getaffil(bud, p_res->data);
          bool showaffil = (affil != affil_none);
-@@ -2096,12 +2241,12 @@
+@@ -2096,12 +2431,12 @@
        snprintf(buffer, 4095, "[%c] %s", imstatus2char[rstatus],
                 (char*)p_res->data);
        scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
@@ -2163,7 +3657,7 @@
          enum imrole role = buddy_getrole(bud, p_res->data);
          enum imaffiliation affil = buddy_getaffil(bud, p_res->data);
  
-@@ -2145,16 +2290,69 @@
+@@ -2145,16 +2480,69 @@
  
  static void do_rename(char *arg)
  {
@@ -2238,7 +3732,7 @@
    bjid   = buddy_getjid(bud);
    group  = buddy_getgroupname(bud);
    type   = buddy_gettype(bud);
-@@ -2162,11 +2360,13 @@
+@@ -2162,11 +2550,13 @@
  
    if (type & ROSTER_TYPE_SPECIAL) {
      scr_LogPrint(LPRINT_NORMAL, "You can't rename this item.");
@@ -2253,7 +3747,7 @@
      return;
    }
  
-@@ -2181,90 +2381,117 @@
+@@ -2181,90 +2571,117 @@
    //  }
    //}
  
@@ -2402,7 +3896,7 @@
      } else {
        // This is a local item, we move it without adding to roster.
        guint msgflag;
-@@ -2276,7 +2503,7 @@
+@@ -2276,7 +2693,7 @@
        msgflag = buddy_getflags(bud) & ROSTER_FLAG_MSG;
        if (msgflag)
          roster_msg_setflag(bjid, FALSE, FALSE);
@@ -2411,7 +3905,7 @@
        if (msgflag)
          roster_msg_setflag(bjid, FALSE, TRUE);
        if ((type & ROSTER_TYPE_ROOM) && xmpp_is_bookmarked(bjid) &&
-@@ -2285,8 +2512,7 @@
+@@ -2285,8 +2702,7 @@
      }
    }
  
@@ -2421,7 +3915,7 @@
    update_roster = TRUE;
  }
  
-@@ -2468,50 +2694,33 @@
+@@ -2468,50 +2884,33 @@
  
  static void do_rawxml(char *arg)
  {
@@ -2492,7 +3986,7 @@
  }
  
  //  check_room_subcommand(arg, param_needed, buddy_must_be_a_room)
-@@ -2874,7 +3083,7 @@
+@@ -2874,7 +3273,7 @@
    fjid_utf8 = g_strdup_printf("%s/%s", buddy_getjid(bud), nick_utf8);
    g_free (nick_utf8);
    msg = to_utf8(arg);
@@ -2501,7 +3995,215 @@
    g_free(fjid_utf8);
    g_free(msg);
    free_arg_lst(paramlst);
-@@ -3347,7 +3556,7 @@
+@@ -3290,6 +3689,207 @@
+ 
+ static void do_room(char *arg)
+ {
++  enum room_scmd_t {
++    room_scmd_join, room_scmd_leave,
++    room_scmd_names,
++    room_scmd_nick,
++    room_scmd_privmsg,
++    room_scmd_remove,
++    room_scmd_topic,
++    room_scmd_unlock,
++    room_scmd_destroy,
++    room_scmd_whois,
++    room_scmd_ban, room_scmd_unban,
++    room_scmd_invite, room_scmd_kick,
++    room_scmd_role, room_scmd_affil,
++    room_scmd_setopt, room_scmd_bookmark,
++  } subcmd;
++  cmdopts_t options = {
++    "room",
++    (cmdopt_t[1]){
++      { CMDOPT_LAST, 'j', "jid", { .opt = NULL } },
++    },
++    (cmdarg_t[1]){
++      { CMDOPT_REQUIRED | CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
++    },
++    (cmdopts_t[18]){
++      { "join", NULL,
++        (cmdarg_t[3]){
++          { 0,           { .arg = "." } },  // room jid
++          { 0,           { .arg = NULL } }, // nick
++          { CMDOPT_LAST, { .arg = NULL } }, // password
++        },
++        NULL, (gpointer)room_scmd_join, 0 },
++      { "leave", NULL,
++        (cmdarg_t[1]){
++          // reason
++          { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
++        },
++        NULL, (gpointer)room_scmd_join, 0 },
++      { "names",
++        (cmdopt_t[4]){
++          { CMDOPT_SWITCH,               'd', "detail",  { .swc = 0 } },
++          { CMDOPT_SWITCH,               's', "short",   { .swc = 0 } },
++          { CMDOPT_SWITCH,               'q', "quiet",   { .swc = 0 } },
++          { CMDOPT_SWITCH | CMDOPT_LAST, 'c', "compact", { .swc = 0 } },
++        },
++        NULL, NULL, (gpointe)room_scmd_names, 0 },
++      { "nick", NULL,
++        (cmdarg_t[1]){
++          // nick
++          { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
++        },
++        NULL, (gpointer)room_scmd_nick, 0 },
++      { "privmsg", NULL,
++        (cmdarg_t[2]) {
++          // nick
++          { CMDOPT_REQUIRED, { .arg = NULL } },
++          // message
++          { CMDOPT_REQUIRED | CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST,
++            { .arg = NULL } },
++        }
++        NULL, (gpointer)room_scmd_privmsg, 0 },
++      { "remove", NULL, NULL, NULL, (gpointer)room_scmd_remove, 0 },
++      { "topic", NULL,
++        (cmdarg_t[1]){
++          // topic
++          { CMDOPT_REQUIRED | CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST,
++            { .arg = NULL } },
++        },
++        NULL, (gpointer)room_scmd_topic, 0 },
++      { "unlock", NULL, NULL, NULL, (gpointer)room_scmd_unlock, 0 },
++      { "destroy", NULL,
++        (cmdarg_t[1]){
++          { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
++        },
++        NULL, (gpointer)room_scmd_destroy, 0 },
++      { "whois", NULL,
++        (cmdarg_t[1]){
++          // nick
++          { CMDOPT_REQUIRED | CMDOPT_LAST, { .arg = NULL } },
++        }, NULL, (gpointer)room_scmd_whois, 0 },
++      { "ban", NULL,
++        (cmdarg_t[2]){
++          // nick
++          { CMDOPT_REQUIRED,                              { .arg = NULL } },
++          // reason
++          { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
++        }, NULL, (gpointer)room_scmd_ban, 0 },
++      { "unban", NULL,
++        (cmdarg_t[1]){
++          // nick
++          { CMDOPT_REQUIRED | CMDOPT_LAST, { .arg = NULL } },
++        }, NULL, (gpointer)room_scmd_unban, 0 },
++      { "invite", NULL, // XXX [-d|direct] ?
++        (cmdarg_t[2]){
++          // jid
++          { CMDOPT_REQUIRED,                              { .arg = NULL } },
++          // reason
++          { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
++        },
++        NULL, (gpointer)room_scmd_invite, 0 },
++      { "kick", NULL,
++        (cmdarg_t[2]){
++          // jid
++          { CMDOPT_REQUIRED,                              { .arg = NULL } },
++          // reason
++          { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
++        },
++        NULL, (gpointer)room_scmd_kick, 0 },
++      { "role", NULL,
++        (cmdarg_t[3]){
++          // jid
++          { CMDOPT_REQUIRED,                              { .arg = NULL } },
++          // role
++          { CMDOPT_REQUIRED,                              { .arg = NULL } },
++          // reason
++          { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
++        }, NULL, (gpointer)room_scmd_role, 0 },
++      { "affil", NULL,
++        (cmdarg_t[3]){
++          // jid
++          { CMDOPT_REQUIRED,                              { .arg = NULL } },
++          // affiliation
++          { CMDOPT_REQUIRED,                              { .arg = NULL } },
++          // reason
++          { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
++        }, NULL, (gpointer)room_scmd_affil, 0 },
++      // XXX below goes some crazy subcommands stuff, that should be rather static lists
++      { "setopt", NULL,
++        (cmdarg_t[1]){
++          { CMDOPT_REQUIRED | CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
++        },
++        (cmdopts_t[3]){
++          { "print_status", NULL,
++            (cmdarg_t[1]){
++              { CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
++            },
++            (cmdopts_t[4]){
++              { "default",    NULL, NULL, NULL, NULL, 0 },
++              { "none",       NULL, NULL, NULL, NULL, 0 },
++              { "in_and_out", NULL, NULL, NULL, NULL, 0 },
++              { "all",        NULL, NULL, NULL, NULL, CMDOPT_LAST },
++            }, NULL, 0 },
++          { "auto_whois", NULL,
++            (cmdarg_t[1]){
++              { CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
++            },
++            (cmdopts_t[3]){
++              { "default", NULL, NULL, NULL, NULL, 0 },
++              { "off",     NULL, NULL, NULL, NULL, 0 },
++              { "on",      NULL, NULL, NULL, NULL, CMDOPT_LAST },
++            }, NULL, 0 },
++          { "flag_joins", NULL,
++            (cmdarg_t[1]){
++              { CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
++            },
++            (cmdopts_t[4]){
++              { "default", NULL, NULL, NULL, NULL, 0 },
++              { "none",    NULL, NULL, NULL, NULL, 0 },
++              { "joins",   NULL, NULL, NULL, NULL, 0 },
++              { "all",     NULL, NULL, NULL, NULL, CMDOPT_LAST },
++            }, NULL, CMDOPT_LAST },
++        }, (gpointer)room_scmd_setopt, 0 },
++      { "bookmark", NULL,
++        (cmdarg_t[1]){
++          { CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
++        },
++        (cmdopts_t[2]){
++          { "add", NULL,
++            (cmdarg_t[1]){
++              { CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
++            },
++            (cmdopts_t[2]){
++              { "+autojoin", NULL,
++                (cmdarg_t[1]){
++                  // nick
++                  { CMDARG_LAST, { .arg = NULL } },
++                }, NULL, NULL, 0 },
++              { "-autojoin", NULL,
++                (cmdarg_t[1]){
++                  // nick
++                  { CMDARG_LAST, { .arg = NULL } },
++                }, NULL, NULL, CMDOPT_LAST },
++            }, NULL, 0 },
++          { "del", NULL,
++            (cmdarg_t[1]){
++              { CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
++            },
++            (cmdopts_t[2]){
++              { "+autojoin", NULL,
++                (cmdarg_t[1]){
++                  // nick
++                  { CMDARG_LAST, { .arg = NULL } },
++                }, NULL, NULL, 0 },
++              { "-autojoin", NULL,
++                (cmdarg_t[1]){
++                  // nick
++                  { CMDARG_LAST, { .arg = NULL } },
++                }, NULL, NULL, CMDOPT_LAST },
++            }, NULL, 0 },
++        }, (gpointer)room_scmd_bookmark, CMDOPT_LAST },
++    },
++  };
+   char **paramlst;
+   char *subcmd;
+   gpointer bud;
+@@ -3347,7 +3947,7 @@
        cmd_room_leave(bud, arg);
    } else if (!strcasecmp(subcmd, "names"))  {
      if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
@@ -2510,23 +4212,236 @@
    } else if (!strcasecmp(subcmd, "nick"))  {
      if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
        room_nick(bud, arg);
+@@ -4162,5 +4762,6 @@
+   }
+   mcabber_set_terminate_ui();
+ }
++#endif
+ 
+ /* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2:  For Vim users... */
 diff -r 92fa48ef53c9 mcabber/mcabber/commands.h
 --- a/mcabber/mcabber/commands.h	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/mcabber/commands.h	Wed Feb 27 23:34:50 2013 +0200
-@@ -14,6 +14,12 @@
-   gpointer userdata;
- } cmd;
++++ b/mcabber/mcabber/commands.h	Tue Mar 05 01:08:31 2013 +0200
+@@ -5,32 +5,209 @@
+ 
+ #include <mcabber/config.h>
+ 
+-// Command structure
+-typedef struct {
+-  char name[32];
+-  const char *help;
+-  guint completion_flags[2];
+-  void (*func)(char *);
+-  gpointer userdata;
+-} cmd;
++//
++//  TODO:
++//
++// * Clustering of options/switches
++// * Multivalue options
++//   - list
++//     - special checker
++//     - or run checker multiple times
++//   - or run command several times
++//     - what if there are several of such arguments?
++// * --help
++//   - use argument type names and clarify, if needed in descriptions
++//   - command/subcommand descriptions
++//   - or maybe just do "/help foo"
++// * Add argument type field and subsystem to check values. Uses so far:
++//   - fjid (handle . and ./resource, perform check_jid_syntax())
++//   - bjid (handle ., strip resource, perform check_jid_syntax())
++//   - bjid in roster/buddylist (result - buddy?)
++//   - name in roster/buddylist (result - buddy?)
++//   - group in roster (result - buddy?)
++//   - other non-space string (strip trailing/starting spaces)
++//     - statusmask (restrictive, check symbols)
++//     - jidmask (non-restrictive, compl: inform what jids match)
++//     - search string (non-restrictive, compl: inform what matches, if possible)
++//     - rosternote (non-restrictive)
++//     - color (restrictive, semi-defined list)
++//     - nick (non-restrictive)
++//     - defined lists (restrictive)
++//     - date (restrictive)
++//     - percent (numerical, restrictive)
++//   - number (result - int)
++//   - filename (expand_filename(), g_utf8_to_filename()?)
++//   - custom (maybe some helpers for constant stringlists)
++//   - text message (compl: nicks in rooms, dictionary?)
++// * Non-argument checks for commands/subcommands:
++//   - xmpp_is_online()
++//   - current_buddy
++// * Subcommands with fallback to argument?
++// * [+foo|-foo] support?
++// * Usable subsystem for completion, based on user-supplied completors
  
+-void cmd_init(void);
+-cmd *cmd_get(const char *command);
+-int  process_line(const char *line);
+-int  process_command(const char *line, guint iscmd);
+-char *expandalias(const char *line);
+-#ifdef MODULES_ENABLE
+-gpointer cmd_del(gpointer id);
+-gpointer cmd_add(const char *name, const char *help, guint flags1, guint flags2,
+-                 void (*f)(char*), gpointer userdata);
+-gboolean cmd_set_safe(const gchar *name, gboolean safe);
++// * for now we'll do without multi-options.
++// * and without clustering.
++// * checks
++//   - argument type == public struct with name and callbacks, simple!
++//   - command checks should be just single callbacks
++// * now, what checker needs?
++//   - [in] value string (current or default)
++//   - [in] additional input data for some generic checkers (like string2enum wordlist)
++//   - [out] replacement value (not necessary string)
++//     - replacement value may need freeing
++//   - [out] error string
++//     - that may need freeing
++
++typedef struct cmdopts_struct      cmdopts_t;
++typedef struct cmdopt_struct       cmdopt_t;
++typedef struct cmdarg_struct       cmdarg_t;
++typedef struct cmdarg_value_struct cmdarg_value_t;
++typedef struct cmdarg_type_struct  cmdarg_type_t;
++
++// note, this is called before options are parsed!
++typedef gchar *(*cmd_checker_t)(cmdopts_t *opts);
++// command function itself
++typedef gchar *(*cmd_handler_t)(cmdopts_t *opts);
++// should check arg -> val -> val.arg and replace, if needed
++typedef gchar *(*cmdarg_checker_t)(cmdarg_t *arg);
++// free resources, used in arg -> val -> val.arg, if needed
++typedef void (*cmdarg_destructor_t)(cmdarg_t *arg);
++// todo
++typedef void (*cmdarg_completor_t)(void); // FIXME
++
++typedef union {
++  guint       uint;        // unsigned integer
++  gint        sint;        // signed integer
++  guint       swc;         // switch count
++  const gchar *roarg;      // default value
++  gchar       *arg;        // string argument
++  cmdopts_t   *cmd;        // subcommand
++  gpointer    bud;         // buddy data (roster entry)
++  gpointer    ptr;         // anything else
++} cmdarg_value_t;
++
++typedef enum {
++  cmd_default     = 0,     // no flags
++  cmd_safe        = 1<<0,  // command is safe to use in mcabberrc
++} cmd_flags_t;
++typedef enum {
++  cmdopt_default  = 0,     // no flags
++  cmdopt_switch   = 1<<0,  // option have no argument
++} cmdopt_flags_t;
++typedef enum {
++  cmdarg_default  = 0,     // no flags
++  cmdarg_catchall = 1<<0,  // argument consumes the end of command line
++  cmdarg_plain    = 1<<1,  // quotes and escapes are not processed
++  cmdarg_subcmd   = 1<<2,  // argument is subcommand
++  cmdarg_check    = 1<<3,  // force checker call on argument
++  cmdarg_visited  = 1<<4,  // private, marks initialized arguments
++  cmdarg_checked  = 1<<5,  // private, marks checked argument
++} cmdarg_flags_t;
++
++struct cmdopts_struct {
++  const char    *name;     // [user,req] command name (error messages, help, subcommands)
++  cmd_flags_t   flags;     // [user,req] safe
++  cmd_checker_t check;     // [user,req] checker routine
++  cmd_handler_t handle;    // [user,req] main command processing function
++  cmdopt_t      **opts;    // [user,req] options
++  cmdarg_t      **args;    // [user,req] arguments
++  cmdopts_t     **cmds;    // [user,req] subcommands
++  gpointer      userdata;  // [user]
++};
++struct cmdopt_struct {
++  cmdopt_flags_t flags;    // [user,req] switch
++  char           shortopt; // [user,req]
++  const char     *longopt; // [user,req]
++  cmdarg_t       arg;      // [user,req]
++};
++struct cmdarg_struct {
++  cmdarg_flags_t flags;    // [user,req] catchall, plain, required, subcommand
++  const char     *defval;  // [user,req] default value
++  cmdarg_type_t  *type;    // [user,req] type cbs - checker and completor
++  gpointer       chkdata;  // [user] instance data for type checker - eg string2enum list
++  gpointer       userdata; // [user]
++  cmdarg_value_t value;    // [parser,chk] current value
++};
++
++struct cmdarg_type_struct {
++  const char          *name;    // [user,req] type name (help)
++  cmdarg_checker_t    check;    // [user,req] check string and set argument value
++  cmdarg_destructor_t free;     // [user,req] free argument value
++  cmdarg_completor_t  complete; // [user,req]
++};
++
++void cmd_define (cmdopts_t *command);
++void cmd_undef (cmdopts_t *command);
++
++//  error cmdopts_parse_argument ( startptr, endptr, flags )
++// Parses next argument according to flags and finishes it with zero.
++// Updates current/end pointers. Parsed string MUST be writable.
++// String may shrink in size (quotation/escapes), thus endpointer is also
++// updated.
++const char *cmdopts_parse_argument(gchar **pr, gchar *er, cmdarg_flags_t flags);
++
++typedef enum {
++  cmdexe_default        = 0,    // no flags
++  cmdexe_check_safe     = 1<<0, // we're parsing main config file
++  cmdexe_check_verbatim = 1<<1, // check for verbatim input mode
++} cmdexe_flags_t;
++typedef enum {
++  cmd_result_ok   = 0,       // all is ok
++  cmd_result_syntax_error,   // syntax, environment or argument check error
++  cmd_result_not_found,      // no such command
++  cmd_result_runtime_error,  // error while executing command
++  cmd_result_verbatim,       // we're in verbatim mode and this is not "msay"
++  cmd_result_input,          // [process_line only] line was a message
++  cmd_result_quit = 255,     // this is hard-coded "quit" command
++} cmd_result_t;
++
++//  error cmd_execute ( command, flags )
++// Checks for "quit", "msay" in verbatim mode, expands aliases, checks command
++// for safety of execution, parses command's arguments according to definition,
++// executes command function. Returns cmdexe_result_ok (0) if all was ok.
++// Non-zero values mean, that command was not executed for some reason.
++// One special value is cmd_result_quit (255) - if it is "quit" command.
++cmd_result_t cmd_execute (gchar *commandline, cmdexe_flags_t flags);
++
++//  process_line(line)
++// Process a command/message line. If this isn't a command, this is a message
++// and it is sent to the currently selected buddy.
++// Returns 255 if the line is the /quit command, 0 on success and some other
++// error codes.
++cmd_result_t process_line(const char *line);
++
++//
++//  Private
++//
++
++void cmd_init (void);
++void cmd_uninit (void);
++
++#if 0
++//  error cmdopts_parse_internal ( startptr, endptr, commanddef )
++// Parses command arguments according to command definition.
++// Parsed string MUST be writable. Regardless of success or error, input
++// string should be considered corrupted by parsing process.
++// Even in case of error, commanddef should be passed to cmdopts_free().
++gchar *cmdopts_parse_internal(gchar **pr, gchar **er, cmdopts_t *command);
++
++//  cmdopts_free ( commanddef )
++// Free various parser data, used in parsing process
++void cmdopts_free(cmdopts_t *command);
+ #endif
+-gboolean cmd_is_safe(const gchar *name);
++
 +typedef enum {
 +  msgtype_not_set,
 +  msgtype_normal,
 +  msgtype_headline,
 +} msgtype_t;
-+
- void cmd_init(void);
- cmd *cmd_get(const char *command);
- int  process_line(const char *line);
-@@ -29,8 +35,9 @@
  
  void cmd_room_whois(gpointer bud, const char *nick, guint interactive);
  void cmd_room_leave(gpointer bud, char *arg);
@@ -2540,7 +4455,7 @@
  
 diff -r 92fa48ef53c9 mcabber/mcabber/roster.c
 --- a/mcabber/mcabber/roster.c	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/mcabber/roster.c	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/mcabber/roster.c	Tue Mar 05 01:08:31 2013 +0200
 @@ -1586,13 +1586,14 @@
  // Look for a buddy whose name or jid contains string.
  // Search begins at current_buddy; if no match is found in the the buddylist,
@@ -2579,460 +4494,21 @@
        if (found)
          return buddy;
      }
-diff -r 92fa48ef53c9 mcabber/mcabber/utils.c
---- a/mcabber/mcabber/utils.c	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/mcabber/utils.c	Wed Feb 27 23:34:50 2013 +0200
-@@ -555,6 +555,318 @@
-     *str = tolower(*str);
- }
- 
-+// in_space        -> in_space, in_optstart, in_argstart
-+// in_optstart     -> in_shortoptend, in_longoptstart, in_argstart ('-')
-+// in_shortoptend  -> in_space, in_argstart, error
-+// in_longoptstart -> in_longopt, in_space, in_argstart ('---')
-+// in_longopt      -> in_longopt, in_space, error
-+// in_argstart     -> in_arg, success
-+// in_arg          -> in_arg, in_space, error
-+
-+// arguments: rw buffer in utf8, end of buffer pointer, options description struct
-+static const char *cmdopts_parse_internal(gchar *arg, gchar *e, cmdopts_t *options)
-+{
-+  // parser state
-+  enum {
-+    in_space,
-+    in_optstart,
-+    in_shortoptstart,
-+    in_shortoptend,
-+    in_longoptstart,
-+    in_longopt,
-+    in_argstart,
-+    in_arg,
-+  } state = in_space;
-+  // current pointer, start of object pointer
-+  gchar *p, *s;
-+  // inside of quotes in non-plain argument
-+  gboolean quotes = FALSE;
-+  // non-option argument or end-of-options marker encountered
-+  gboolean opts_ended = FALSE;
-+  // option, for which argument is currently parsed
-+  cmdopt_t *option = NULL;
-+  // argument, that we currently parse
-+  cmdarg_t *argument = NULL;
-+  // flags of option/argument
-+  guint flags = 0;
-+  // error message to return
-+  const char *error = NULL;
-+
-+  p = arg;
-+  // we allow parser to do one extra run on final '\0'
-+  while (p <= e && error == NULL) {
-+    if (state == in_space) { // space between args/options
-+      if (*p == ' ' || *p == '\0') { // still space
-+        p ++;
-+      } else if (*p == '-' && !opts_ended) { // option
-+        state = in_optstart;
-+        p ++;
-+      } else { // argument
-+        if (!option) {
-+          opts_ended = TRUE;
-+        }
-+        s = p;
-+        state = in_argstart;
-+      }
-+    } else if (state == in_optstart) { // long/short option
-+      if (*p == ' ' || *p == '\0') { // argument '-'
-+        opts_ended = TRUE;
-+        s = p - 1;
-+        state = in_argstart;
-+      } else if (*p == '-') { // long option
-+        state = in_longoptstart;
-+        p ++;
-+      } else { // short option
-+        s = p;
-+        state = in_shortoptend;
-+        p ++;
-+      }
-+    } else if (state == in_shortoptend) { // short option
-+      if (*p == ' ' || *p == '\0') { // option really ended
-+        gboolean found = FALSE;
-+        option = options -> opts;
-+        if (option) {
-+          do {
-+            if (option -> shortopt == *s) {
-+              found = TRUE;
-+              break;
-+            }
-+          } while ((!(option++ -> flags & CMDOPT_LAST)) && !found);
-+        }
-+        if (found) { // option is known
-+          if (option -> flags & CMDOPT_SWITCH) { // it is switch
-+            if (option -> flags & CMDOPT_CATCHALL) {
-+              option -> value.swc ++;
-+            } else {
-+              option -> value.swc = !option -> value.swc;
-+            }
-+            option = NULL;
-+          } else { // it is option
-+            if (*p == '\0') {
-+              error = "Short option argument not specified";
-+            }
-+          }
-+          state = in_space;
-+          p ++;
-+        } else { // option is unknown
-+          error = "Unknown short option";
-+        }
-+      } else { // short option not ended
-+        // consider it argument (to allow -autojoin)
-+        p --;
-+        opts_ended = TRUE;
-+        s = p - 1;
-+        state = in_argstart;
-+      }
-+    } else if (state == in_longoptstart) { // long option initialization
-+      if (*p == ' ' || *p == '\0') { // end of options '--'
-+        opts_ended = TRUE;
-+        state = in_space;
-+        p ++;
-+      } else if (*p == '-') { // argument, starting with '---'
-+        opts_ended = TRUE;
-+        s = p - 2;
-+        state = in_argstart;
-+      } else { // it is long option
-+        s = p;
-+        state = in_longopt;
-+        p ++;
-+      }
-+    } else if (state == in_longopt) { // long option name
-+      if (*p == ' ' || *p == '\0') { // long option ended
-+        gboolean found = FALSE;
-+        gboolean eof = *p == '\0';
-+        *p = '\0';
-+        option = options -> opts;
-+        if (option) {
-+          do {
-+            if (!g_strcmp0 (option -> longopt, s)) {
-+              found = TRUE;
-+              break;
-+            }
-+          } while ((!(option++ -> flags & CMDOPT_LAST)) && !found);
-+        }
-+        if (found) { // option is known
-+          if (option -> flags & CMDOPT_SWITCH) { // it is switch
-+            if (option -> flags & CMDOPT_CATCHALL) {
-+              option -> value.swc ++;
-+            } else {
-+              option -> value.swc = !option -> value.swc;
-+            }
-+            option = NULL;
-+          } else { // it is option
-+            if (eof) {
-+              error = "Long option argument not specified";
-+            }
-+          }
-+          state = in_space;
-+          p ++;
-+        } else { // option is unknown
-+          error = "Unknown long option";
-+        }
-+      } else { // still long option
-+        p ++;
-+      }
-+    } else if (state == in_argstart) { // option/command argument initialization
-+      if (option) {
-+        flags = option -> flags & ~CMDOPT_CATCHALL; // catchall in options indicates multi-options
-+      } else {
-+        if (!argument) {
-+          argument = options -> args;
-+        }
-+        if (!argument) { // no need to parse arguments at all
-+          break;
-+        }
-+        flags = argument -> flags;
-+        if ((flags & CMDOPT_CATCHALL) && (flags & CMDOPT_PLAIN)) { // can finish right away
-+          argument -> value.arg = s;
-+          break;
-+        }
-+      }
-+      quotes = FALSE;
-+      state = in_arg;
-+    } else if (state == in_arg) { // option/command argument value
-+      if (*p == '\0' && quotes) { // end of line in quotes
-+        error = "Unfinished quoted argument";
-+      } else if ((*p == ' ' && (!quotes) && !(flags & CMDOPT_CATCHALL)) || *p == '\0') { // argument ended
-+        if (*p != '\0') {
-+          *p = '\0';
-+          p ++;
-+        } 
-+        if (option) { // option argument
-+          if (option -> flags & CMDOPT_CATCHALL) { // multi-value option
-+            option -> value.multiopt = g_slist_append (option -> value.multiopt, s);
-+          } else { // single-value option
-+            option -> value.opt = s;
-+          }
-+          option = NULL;
-+        } else { // command argument
-+          if (argument -> flags & CMDOPT_SUBCOMMAND) {
-+            gboolean found = FALSE;
-+            cmdopts_t *subcommand = options -> cmds;
-+            if (subcommand) {
-+              do {
-+                if (!g_strcmp0(s, subcommand -> name)) {
-+                  found = TRUE;
-+                  break;
-+                }
-+              } while (!(subcommand++ -> flags & CMDOPT_LAST));
-+            }
-+            if (found) {
-+              argument -> value.cmd = subcommand;
-+              error = cmdopts_parse_internal(p, e, subcommand);
-+              break;
-+            } else {
-+              error = "Unknown subcommand";
-+            }
-+          } else {
-+            argument -> value.arg = s;
-+            if (argument -> flags & CMDOPT_LAST) { // last argument
-+              break;
-+            }
-+            argument ++;
-+          }
-+        }
-+        state = in_space;
-+      } else if (*p == '\\' && !(flags & CMDOPT_PLAIN)) { // next char escape
-+        memmove(p, p+1, e-(p+1)+1);
-+        e --;
-+        if (*p == '\0') {
-+          error = "Escape at the end of line";
-+        }
-+        p ++;
-+      } else if (*p == '"' && !(flags & CMDOPT_PLAIN)) { // quotation start/end
-+        memmove(p, p+1, e-(p+1)+1);
-+        e --;
-+        quotes = !quotes;
-+      } else { // still argument
-+        p ++;
-+      }
-+    }
-+  }
-+
-+  // check required flags on options
-+  if (error == NULL && options -> opts) {
-+    option = options -> opts;
-+    do {
-+      if (option -> flags & CMDOPT_REQUIRED) {
-+        if (option -> flags & CMDOPT_SWITCH) {
-+          // no way to check trigger switches, but no point in it as well
-+          if (option -> flags & CMDOPT_CATCHALL && option -> value.swc == 0) {
-+            error = "Required switch is not specified";
-+            break;
-+          }
-+        } else {
-+          if ((option -> flags & CMDOPT_CATCHALL && option -> value.multiopt == NULL) ||
-+              ((!(option -> flags & CMDOPT_CATCHALL)) && option -> value.opt == NULL)) {
-+            error = "Required option is not specified";
-+            break;
-+          }
-+        }
-+      }
-+    } while (!(option++ -> flags & CMDOPT_LAST));
-+  }
-+
-+  // check required flags on arguments
-+  if (error == NULL && options -> args) {
-+    argument = options -> args;
-+    do {
-+      if (argument -> flags & CMDOPT_REQUIRED) {
-+        if (argument -> flags & CMDOPT_SUBCOMMAND) {
-+          if (argument -> value.cmd == NULL) {
-+            error = "Subcommand is not specified";
-+            break;
-+          }
-+        } else {
-+          if (argument -> value.arg == NULL) {
-+            error = "Required argument is not specified";
-+            break;
-+          }
-+        }
-+      }
-+    } while (!(argument++ -> flags & CMDOPT_LAST));
-+  }
-+
-+  return error;
-+}
-+
-+const char *cmdopts_parse(const char *arg, cmdopts_t *options)
-+{
-+  gchar *utf8 = to_utf8(arg);
-+  gchar *e;
-+  const char *error;
-+
-+  for (e = utf8; *e; e++);
-+  options -> freeme = utf8;
-+  error = cmdopts_parse_internal(utf8, e, options);
-+  if (error) {
-+    scr_log_print(LPRINT_NORMAL, "%s: %s", options -> name, error);
-+    cmdopts_free(options);
-+  }
-+  return error;
-+}
-+
-+void cmdopts_free(cmdopts_t *options)
-+{
-+  cmdopt_t *option = options -> opts;
-+  cmdopts_t *subcommand = options -> cmds;
-+  if (option) {
-+    do {
-+      if ((option -> flags & (CMDOPT_CATCHALL|CMDOPT_SWITCH)) == CMDOPT_CATCHALL) {
-+        g_slist_free(option -> value.multiopt);
-+        option -> value.multiopt = NULL;
-+      }
-+    } while (!(option++ -> flags & CMDOPT_LAST));
-+  }
-+  if (subcommand) {
-+    do {
-+      cmdopts_free(subcommand);
-+    } while (!(subcommand++ -> flags & CMDOPT_LAST));
-+  }
-+  g_free(options -> freeme);
-+  options -> freeme = NULL;
-+}
-+
- //  strip_arg_special_chars(string)
- // Remove quotes and backslashes before an escaped quote
- // Only quotes need a backslash
-diff -r 92fa48ef53c9 mcabber/mcabber/utils.h
---- a/mcabber/mcabber/utils.h	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/mcabber/utils.h	Wed Feb 27 23:34:50 2013 +0200
-@@ -43,6 +43,125 @@
- char **split_arg(const char *arg, unsigned int n, int dontstriplast);
- void free_arg_lst(char **arglst);
- 
-+//  error cmdopts_parse (argstring, optionlist)
-+// Function parses command argument string according to provided list of
-+// options and arguments. If in this process it encounters an error, it
-+// returns error string (that should be displayed and g_free'd afterwards).
-+// Note: For now returned error is constant string, that shouldn't be freed,
-+// but we're getting there.
-+// After processing you should free freeme and any GSList values of catchall
-+// options (only lists itself, not values). For your convenience, there is
-+// cmdopts_free(), that does exactly that.
-+// The function recognizes four kinds of expressions:
-+//  - Options with arguments in a form '-f bar' or '--foo bar'
-+//  - Switches without arguments in a form '-f' or '--foo'
-+//  - End-of-options marker '--'
-+//  - Individual arguments ('-' and '---' are considered arguments too)
-+// To define command line syntax, you pass cmdopts_t struct, that contains
-+// two contiguous lists of cmdopt_t and cmdarg_t structs accordingly. The
-+// last struct in list must have CMDOPT_LAST flag set.
-+// You can specify your own default values, they will be replaced/appended
-+// if needed.
-+// You can omit specifying longopt or shortopt (put NULL or '\0' there).
-+// Note: returned values and arguments are already converted to utf8.
-+
-+// Flags:
-+// Only applies to options, defined if option does not have argument.
-+#define CMDOPT_SWITCH     ( 1<<0 )
-+// Don't process quotes and escapes in argument (applies to option arguments too).
-+#define CMDOPT_PLAIN      ( 1<<1 )
-+// For options   - put all encountered values into GSList value.multiopt
-+//                 instead of overwriting value.opt.
-+// For switches  - increment value.swc instead of logical flipping.
-+// For arguments - grab the rest of the line without splitting on spaces.
-+// Implicitly last argument.
-+#define CMDOPT_CATCHALL   ( 1<<2 )
-+// Option/argument must have value.
-+#define CMDOPT_REQUIRED   ( 1<<3 )
-+// Last entry in struct sequence.
-+#define CMDOPT_LAST       ( 1<<4 )
-+// Argument only, argument is the name for subcommand.
-+// Implicitly last argument.
-+#define CMDOPT_SUBCOMMAND ( 1<<5 )
-+
-+// TODO:
-+// * Options/arguments value should become 'default' field, value field will go
-+//   later, so that user will not need to initialize it.
-+// * Clustering of options/switches
-+// * --help
-+//   * Add argument names for --help.
-+//   * Argument or command/subcommand descriptions for --help?
-+//   * Or maybe just do "/help foo"?
-+// * Add argument type field and subsystem to check values. Uses so far:
-+//   - fjid (handle . and ./resource, perform check_jid_syntax())
-+//   - bjid (handle ., strip resource, perform check_jid_syntax())
-+//   - bjid in roster/buddylist (result - buddy?)
-+//   - name in roster/buddylist (result - buddy?)
-+//   - group in roster (result - buddy?)
-+//   - other non-space string (strip trailing/starting spaces)
-+//     - statusmask (restrictive)
-+//     - jidmask (non-restrictive)
-+//     - search string
-+//     - rosternote
-+//     - color (restrictive, semi-defined list)
-+//     - nick (non-restrictive)
-+//     - defined lists (restrictive)
-+//     - date (restrictive)
-+//     - percent (numerical, restrictive)
-+//   - number (result - int)
-+//   - filename (expand_filename(), g_utf8_to_filename()?)
-+//   - custom (maybe some helpers for constant stringlists)
-+//   - text message (complete nicks in rooms)
-+// * Add non-argument checks for commands/subcommands:
-+//   - xmpp_is_online()
-+//   - current_buddy
-+// * --help generates error with short usage, based on info in options struct.
-+// * Subcommands with fallback to argument?
-+// * [+foo|-foo] support?
-+// * Integrate with commands
-+// * Integrate with completion
-+// * Usable subsystem for completion, based on user-supplied argument types
-+// * Update docs above
-+
-+// so, the process of command execution would look like:
-+// - we walk through the options, set default values
-+// - we parse argument string, populating options  - on this or on next step
-+// - we check for required options availability    - we can call generic argument check routine, based on argument type
-+// - we call callback
-+// - we free resources
-+typedef struct cmdopts_struct cmdopts_t;
-+typedef union {
-+  GSList *multiopt;
-+  gchar  *opt;
-+  guint  swc;
-+} cmdopt_value_t;
-+typedef struct {
-+  guint          flags;
-+  char           shortopt;
-+  const char     *longopt;
-+  cmdopt_value_t value;
-+} cmdopt_t;
-+typedef union {
-+  gchar     *arg;
-+  cmdopts_t *cmd;
-+} cmdarg_value_t;
-+typedef struct {
-+  guint          flags;
-+  cmdarg_value_t value;
-+} cmdarg_t;
-+struct cmdopts_struct {
-+  const char *name;
-+  cmdopt_t   *opts;
-+  cmdarg_t   *args;
-+  cmdopts_t  *cmds;
-+  gpointer   userdata; // unused, for user's convenience
-+  guint      flags;    // only meaningful for subcommands
-+  gchar      *freeme;  // private
-+};
-+
-+const char *cmdopts_parse (const char *arg, cmdopts_t *options);
-+void cmdopts_free(cmdopts_t *options);
-+
- void replace_nl_with_dots(char *bufstr);
- char *ut_expand_tabs(const char *text);
- char *ut_unescape_tabs_cr(const char *text);
+diff -r 92fa48ef53c9 mcabber/mcabber/screen.c
+--- a/mcabber/mcabber/screen.c	Sun Jan 27 00:40:37 2013 +0200
++++ b/mcabber/mcabber/screen.c	Tue Mar 05 01:08:31 2013 +0200
+@@ -3626,7 +3626,7 @@
+ {
+   scr_check_auto_away(TRUE);
+   last_activity_buddy = current_buddy;
+-  if (process_line(inputLine))
++  if (process_line(inputLine) == cmd_result_quit)
+     return 255;
+   // Add line to history
+   scr_cmdhisto_addline(inputLine);
 diff -r 92fa48ef53c9 mcabber/mcabber/xmpp_iq.c
 --- a/mcabber/mcabber/xmpp_iq.c	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/mcabber/xmpp_iq.c	Wed Feb 27 23:34:50 2013 +0200
++++ b/mcabber/mcabber/xmpp_iq.c	Tue Mar 05 01:08:31 2013 +0200
 @@ -289,10 +289,7 @@
        if (value) {
          for (s = adhoc_status_list; !s->name || strcmp(s->name, value); s++);