*** empty log message ***
[m17n/m17n-lib-js.git] / xex.js
diff --git a/xex.js b/xex.js
index a86a5b2..68e38df 100644 (file)
--- a/xex.js
+++ b/xex.js
@@ -1,6 +1,18 @@
 // -* coding: utf-8; -*
 
-var Xex = {};
+var Xex = {
+  LogNode: null,
+  Log: function (arg, indent)
+  {
+    if (! Xex.LogNode)
+      return;
+    var str = '';
+    if (indent != undefined)
+      for (var i = 0; i <= indent; i++)
+       str += '  ';
+    Xex.LogNode.value = str + arg + "\n" + Xex.LogNode.value;
+  }
+};
 
 Xex.Error = {
   UnknownError: "unknown-error",
@@ -74,7 +86,7 @@ Xex.Subrountine = function (builtin, name, with_var, min_args, max_args)
 
 Xex.Subrountine.prototype.Call = function (domain, vari, args)
 {
-  newargs = new Array ();
+  var newargs = new Array ();
   for (var i = 0; i < args.length; i++)
     {
       newargs[i] = args[i].Eval (domain);
@@ -171,9 +183,9 @@ Xex.Macro.prototype.Call = function (domain, vari, args)
       domain.Bind (this.args[i], args[i]);
     try {
       domain.Catch (Xex.CatchTag.Return);
-      for (var term in body)
+      for (var i in this.body)
         {
-         result = term.Eval (domain);
+         result = this.body[i].Eval (domain);
          if (domain.Thrown ())
            break;
        }
@@ -352,7 +364,7 @@ Xex.Domain.prototype = {
   },
   Defvar: function (name, desc, val, range)
   {
-    var vari = new Xex.Variable (name, desc, val, range);
+    var vari = new Xex.Variable (this, name, desc, val, range);
     this.variables[name] = vari;
     return vari;
   },
@@ -388,12 +400,7 @@ Xex.Domain.prototype = {
   {
     values = {};
     for (var elt in this.variables)
-      {
-       if (! this.variables[elt].val)
-         alert ('unknown value of ' + elt);
-       else
-         values[elt] = this.variables[elt].val.Clone ();
-      }
+      values[elt] = this.variables[elt].val.Clone ();
     return values;
   },
   RestoreValues: function (values)
@@ -453,7 +460,7 @@ Node.prototype.nextElement = function ()
     var name = node.attributes['vname'].nodeValue;
     if (! name)
       throw new Xex.ErrTerm (Xex.Error.NoVariableName, node, '');
-    var vari = domain.variables['name'];
+    var vari = domain.variables[name];
     var desc, val, range;
     if (vari)
       {
@@ -570,26 +577,19 @@ Node.prototype.nextElement = function ()
        if (name == 'defun' || name == 'defmacro')
          {
            name = parse_defun_head (domain, node);
-           MIM.log ('defmacro:' + name);
            parse_defun_body (domain, node);
            return new Xex.StrTerm (name);
          }
        if (name == 'defvar')
          {
            name = parse_defvar (domain, node);
-           MIM.log ('defvar:' + name);
            return new Xex.StrTerm (name);
          }
        return new Xex.Funcall.prototype.Parser (domain, node);
       }
     for (var n = node; n && n != stop; n = n.nextElement ())
-      {
-       if (n.nodeName == 'defun' || n.nodeName == 'defmacro')
-         {
-           var name = parse_defun_head (domain, n);
-           MIM.log ('defmacro:' + name);
-         }
-      }
+      if (n.nodeName == 'defun' || n.nodeName == 'defmacro')
+       parse_defun_head (domain, n);
     var terms = null;
     for (var n = node; n && n != stop; n = n.nextElement ())
       {
@@ -621,6 +621,7 @@ Xex.Varref = function (vname)
   {
     if (! this.vari || this.vari.domain != domain)
       this.vari = domain.GetVarCreate (this.val);
+    Xex.Log (this.ToString () + '=>' + this.vari.val, domain.depth);
     return this.vari.val;
   }
 
@@ -629,6 +630,11 @@ Xex.Varref = function (vname)
     return new Xex.Varref (node.attributes['vname'].nodeValue);
   }
 
+  proto.ToString = function ()
+  {
+    return '<varref vname="' + this.val + '"/>';
+  }
+
   Xex.Varref.prototype = proto;
 }) ();
 
@@ -671,7 +677,16 @@ Xex.Funcall = function (func, vari, args)
 
   proto.Eval = function (domain)
   {
-    return this.func.Call (domain, this.vari, this.args);
+    if (! (this.func instanceof Xex.Subrountine))
+      Xex.Log (this, domain.depth);
+    domain.depth++;
+    var result;
+    try {
+      result = this.func.Call (domain, this.vari, this.args);
+    } finally {
+      Xex.Log (this + ' => ' + result, --domain.depth);
+    }
+    return result;
   }
 
   proto.Clone = function ()
@@ -691,11 +706,18 @@ Xex.Funcall = function (func, vari, args)
   {
     var arglist = ''
     var len = this.args.length;
+    var str = '<' + this.func.name;
+    if (this.vari)
+      str += ' vname="' + this.vari.name + '"';
     if (len == 0)
-      return '<' + this.func.name + '/>';
-    for (var i = 0; i < len; i++)
-      arglist += this.args[i].toString ();
-    return '<' + this.func.name + '>' + arglist + '</' + this.func.name + '>';
+      return str + '/>';
+    if (this.func instanceof Xex.Subrountine)
+      for (var i = 0; i < len; i++)
+       arglist += this.args[i].toString ();
+    else
+      for (var i = 0; i < len; i++)
+       arglist += '.';
+    return str + '>' + arglist + '</' + this.func.name + '>';
   }
 
   Xex.Funcall.prototype = proto;
@@ -837,19 +859,6 @@ Xex.LstTerm = function (list) { this.val = list; };
 
   function Fset (domain, vari, args)
   {
-    return vari.SetValue (args[0]);
-  }
-
-  function maybe_set_intvar (vari, n)
-  {
-    var term = new Xex.IntTerm (n);
-    if (vari != null)
-      vari.SetValue (term);
-    return term;
-  }
-
-  function Fset (domain, vari, args)
-  {
     if (! vari)
       throw new Xex.ErrTerm (Xex.Error.NoVariableName,
                             'No variable name to set');
@@ -859,7 +868,7 @@ Xex.LstTerm = function (list) { this.val = list; };
 
   function maybe_set_intvar (vari, n)
   {
-    var term = new IntTerm (n);
+    var term = new Xex.IntTerm (n);
     if (vari)
       vari.SetValue (term);
     return term;
@@ -1054,9 +1063,9 @@ Xex.LstTerm = function (list) { this.val = list; };
   {
     for (var i = 0; i < args.length; i++)
       {
-       var list = args[i].val;
-       var result = list.val[0].Eval (doamin);
-       if (result.isTrue ())
+       var list = args[i];
+       var result = list.val[0].Eval (domain);
+       if (result.IsTrue ())
          {
            for (var j = 1; j < list.val.length; j++)
              {
@@ -1143,8 +1152,6 @@ Xex.LstTerm = function (list) { this.val = list; };
   Xex.BasicDomain = basic;
 
   basic.DefSubr (Fset, "set", true, 1, 1);
-  if (basic.functions['='])
-    alert (basic.functions['=']);
   basic.DefAlias ("=", "set");
   //basic.DefSubr (Fnot, "not", false, 1, 1);
   //basic.DefAlias ("!", "not");
@@ -1425,24 +1432,16 @@ var MIM = {
   MIM.Marker.prototype.CharAt = function (ic)
   {
     var p = this.Position (ic);
-    if (p < 0)
-      return ic.GetSurroundingChar (p);
-    else if (p >= ic.preedit.length)
-      return ic.GetSurroundingChar (p - ic.preedit.length);
+    if (p < 0 || p >= ic.preedit.length)
+      return 0;
     return ic.preedit.charCodeAt (p);
   }
 
-  MIM.NamedMarker = function (name) { this.val = name; }
-  MIM.NamedMarker.prototype = new MIM.Marker ();
-  MIM.NamedMarker.prototype.Position = function (ic)
-  {
-    var p = ic.marker_positions[this.val];
-    return (p == undefined ? 0 : p);
-  }
-  MIM.NamedMarker.prototype.Mark = function (ic)
-  {
-    ic.marker_positions[this.val] = ic.cursor_pos;
-  }
+  MIM.FloatingMarker = function (name) { this.val = name; };
+  var proto = new MIM.Marker ();
+  MIM.FloatingMarker.prototype = proto;
+  proto.Position = function (ic) { return ic.marker_positions[this.val]; };
+  proto.Mark = function (ic) { ic.marker_positions[this.val] = ic.cursor_pos; };
 
   MIM.PredefinedMarker = function (name) { this.val = name; }
   MIM.PredefinedMarker.prototype = new MIM.Marker ();
@@ -1493,7 +1492,7 @@ var MIM = {
   MIM.SurroundMarker = function (name)
   {
     this.val = name;
-    this.distance = parseInt (name.slice (2));
+    this.distance = parseInt (name.slice (1));
     if (isNaN (this.distance))
       throw new Xex.ErrTerm (MIM.Error.ParseError, "Invalid marker: " + name);
   }
@@ -1502,6 +1501,17 @@ var MIM = {
   {
     return ic.cursor_pos + this.distance;
   }
+  MIM.SurroundMarker.prototype.CharAt = function (ic)
+  {
+    if (this.val == '@-0')
+      return -1;
+    var p = this.Position (ic);
+    if (p < 0)
+      return ic.GetSurroundingChar (p);
+    else if (p >= ic.preedit.length)
+      return ic.GetSurroundingChar (p - ic.preedit.length);
+    return ic.preedit.charCodeAt (p);
+  }
 
   MIM.Marker.prototype.Parser = function (domain, node)
   {
@@ -1516,7 +1526,7 @@ var MIM = {
        throw new Xex.ErrTerm (MIM.Error.ParseError,
                               "Invalid marker: " + name);
       }
-    return new MIM.NamedMarker (name);
+    return new MIM.FloatingMarker (name);;
   }
 }) ();
 
@@ -1631,13 +1641,12 @@ MIM.Keymap = function ()
 {
   this.name = 'TOP';
   this.submaps = null;
-  this.actions = null;
 };
 
 (function () {
   var proto = {};
 
-  function add_rule (keymap, rule)
+  function add_rule (keymap, rule, branch_actions)
   {
     var keyseq = rule.keyseq;
     var len = keyseq.val.length;
@@ -1658,16 +1667,17 @@ MIM.Keymap = function ()
        keymap = sub;
        keymap.name = name;
       }
-    keymap.actions = rule.actions;
+    keymap.map_actions = rule.actions;
+    keymap.branch_actions = branch_actions;
   }
 
-  proto.Add = function (map)
+  proto.Add = function (map, branch_actions)
   {
     var rules = map.rules;
     var len = rules.length;
 
     for (var i = 0; i < len; i++)
-      add_rule (this, rules[i]);
+      add_rule (this, rules[i], branch_actions);
   }
   proto.Lookup = function (keys, index)
   {
@@ -1709,10 +1719,8 @@ MIM.State = function (name)
          {
            var n = node.firstElement ();
            if (node.nodeName == 'branch')
-             {
-               state.keymap.Add (map_list[node.attributes['mname'].nodeValue]);
-               state.keymap.actions = Xex.Term.Parse (domain, n, null);
-             }
+             state.keymap.Add (map_list[node.attributes['mname'].nodeValue],
+                               Xex.Term.Parse (domain, n, null));
            else if (node.nodeName == 'state-hook')
              state.enter_actions = Xex.Term.Parse (domain, n, null);
            else if (node.nodeName == 'catch-all-branch')
@@ -1730,6 +1738,224 @@ MIM.State = function (name)
   MIM.State.prototype = proto;
 }) ();
 
+(function () {
+  function Block (index, term)
+  {
+    this.Index = index;
+    if (term.IsStr)
+      this.Data = term.val;
+    else if (term.IsList)
+      {
+       this.Data = new Array ();
+       for (var i = 0; i < term.val.length; i++)
+         this.Data.push (term.val[i].val);
+      }
+  }
+
+  Block.prototype.Count = function () { return this.Data.length; }
+  Block.prototype.get = function (i)
+  {
+    return (this.Data instanceof Array ? this.Data[i] : this.Data.charAt (i));
+  }
+
+  MIM.Candidates = function (candidates, column)
+  {
+    this.column = column;
+    this.row = 0;
+    this.index = 0;
+    this.total = 0;
+    this.blocks = new Array ();
+
+    for (var i = 0; i < candidates.length; i++)
+      {
+       var block = new Block (this.total, candidates[i]);
+       this.blocks.push (block);
+       this.total += block.Count ();
+      }
+  }
+
+  function get_col ()
+  {
+    return (this.column > 0 ? this.index % this.column
+           : this.index - this.blocks[this.row].Index);
+  }
+
+  function prev_group ()
+  {
+    var col = get_col.call (this);
+    var nitems;
+    if (this.column > 0)
+      {
+       this.index -= this.column;
+       if (this.index >= 0)
+         nitems = this.column;
+       else
+         {
+           var lastcol = (this.total - 1) % this.column;
+           this.index = (col < lastcol ? this.total - lastcol + col
+                         : this.total - 1);
+           this.row = this.blocks.length - 1;
+           nitems = lastcol + 1;
+         }
+       while (this.blocks[this.row].Index > this.index)
+         this.row--;
+      }
+    else
+      {
+       this.row = this.row > 0 ? this.row - 1 : this.blocks.length - 1;
+       nitems = this.blocks[this.row].Count ();
+       this.index = (this.blocks[this.row].Index
+                     + (col < nitems ? col : nitems - 1));
+      }
+    return nitems;
+  }
+
+  function next_group ()
+  {
+    var col = get_col.call (this);
+    var nitems;
+    if (this.column > 0)
+      {
+       this.index += this.column - col;
+       if (this.index < this.total)
+         {
+           if (this.index + col >= this.total)
+             {
+               nitems = this.total - this.index;
+               this.index = this.total - 1;
+             }
+           else
+             {
+               nitems = this.column;
+               this.index += col;
+             }
+         }
+       else
+         {
+           this.index = col;
+           this.row = 0;
+         }
+       while (this.blocks[this.row].Index > this.index)
+         this.row++;
+      }
+    else
+      {
+       this.row = this.row < this.blocks.length - 1 ? this.row + 1 : 0;
+       nitems = this.blocks[this.row].Count ();
+       this.index = (this.blocks[this.row].Index
+                     + (col < nitems ? col : nitems - 1));
+      }
+    return nitems;
+  }
+
+  function prev ()
+  {
+    if (this.index == 0)
+      {
+       this.index = this.total - 1;
+       this.row = this.blocks.length - 1;
+      }
+    else
+      {
+       this.index--;
+       if (this.blocks[this.row].Index > this.index)
+         this.row--;
+      }
+    }
+
+  function next ()
+  {
+    this.index++;
+    if (this.index == this.total)
+      {
+       this.index = 0;
+       this.row = 0;
+      }
+    else
+      {
+       var b = this.blocks[this.row];
+       if (this.index == b.Index + b.Count ())
+         this.row++;
+      }
+  }
+
+  function first ()
+  {
+    this.index -= get_col.call (this);
+    while (this.blocks[this.row].Index > this.index)
+      this.row--;
+  }
+
+  function last ()
+  {
+    var b = this.blocks[this.row];
+    if (this.column > 0)
+      {
+       if (this.index + 1 < this.total)
+         {
+           this.index += this.column - get_col.call (this) + 1;
+           while (b.Index + b.Count () <= this.index)
+             b = this.blocks[++this.row];
+         }
+      }
+    else
+      this.index = b.Index + b.Count () - 1;
+  }
+
+  MIM.Candidates.prototype.Current = function ()
+  {
+    var b = this.blocks[this.row];
+    return b.get (this.index - b.Index);
+  }
+
+  MIM.Candidates.prototype.Select = function (selector)
+  {
+    if (selector.type == 'selector')
+      {
+       switch (selector.val)
+         {
+         case '@<': first.call (this); break;
+         case '@>': last.call (this); break;
+         case '@-': prev.call (this); break;
+         case '@+': next.call (this); break;
+         case '@[': prev_group.call (this); break;
+         case '@]': next_group.cal (this); break;
+         default: break;
+         }
+       return this.Current ();
+      }
+    var col, start, end
+    if (this.column > 0)
+      {
+       col = this.index % this.column;
+       start = this.index - col;
+       end = start + this.column;
+      }
+    else
+      {
+       start = this.blocks[this.row].Index;
+       col = this.index - start;
+       end = start + this.blocks[this.row].Count;
+      }
+    if (end > this.total)
+      end = this.total;
+    this.index += selector.val - col;
+    if (this.index >= end)
+      this.index = end - 1;
+    if (this.column > 0)
+      {
+       if (selector.val > col)
+         while (this.blocks[this.row].Index + this.blocks[this.row].Count
+                < this.index)
+           this.row++;
+       else
+         while (this.blocks[this.row].Index > this.index)
+           this.row--;
+      }
+    return this.Current ();
+  }
+}) ();
+
 MIM.im_domain = new Xex.Domain ('input-method', null, null);
 MIM.im_domain.DefType (MIM.KeySeq.prototype);
 MIM.im_domain.DefType (MIM.Marker.prototype);
@@ -1748,14 +1974,16 @@ MIM.im_domain.DefType (MIM.State.prototype);
       text = String.fromCharCode (args[0].val);
     else
       text = args[0].val;
-    domain.context.insert (text, null);
+    domain.context.ins (text, null);
+    return args[0];
   }
 
   function Finsert_candidates (domain, vari, args)
   {
     var ic = domain.context;
-    var candidates = new Candidates (args, column);
-    ic.insert (candidates.Current (), candidates);
+    var gsize = domain.variables['candidates_group_size'];
+    var candidates = new MIM.Candidates (args, gsize ? gsize.Intval : 0);
+    ic.ins (candidates.Current (), candidates);
     return args[0];
   }
 
@@ -1763,8 +1991,7 @@ MIM.im_domain.DefType (MIM.State.prototype);
   {
     var ic = domain.context;
     var pos = args[0].IsInt ? args[0].Intval : args[0].Position (ic);
-    ic.del (pos);
-    return new Xex.Term (ic.del (pos));
+    return new Xex.IntTerm (ic.del (pos));
   }
 
   function Fselect (domain, vari, args)
@@ -1774,18 +2001,18 @@ MIM.im_domain.DefType (MIM.State.prototype);
 
     if (can)
       {
-       var candidate = can.Current ();
-
-       ic.del (ic.cursor_pos - candidate.length);
-       candidate = can.Select (args[0]);
-       ic.insert (candidate, can);
+       var old_text = can.Current ();
+       var new_text = can.Select (args[0]);
+       ic.rep (old_text, new_text, can);
       }
+    else
+      Xex.Log ('no candidates at ' + ic.cursor_pos + ' of ' + ic.candidate_table.table.length);
     return args[0];
   }
 
   function Fchar_at (domain, vari, args)
   {
-    return new Xex.Term (args[0].CharAt (domain.context));
+    return new Xex.IntTerm (args[0].CharAt (domain.context));
   }
 
   function Fmove (domain, vari, args)
@@ -1793,7 +2020,7 @@ MIM.im_domain.DefType (MIM.State.prototype);
     var ic = domain.context;
     var pos = args[0].IsInt ? args[0].val : args[0].Position (ic);
     ic.move (pos);
-    return args[0];
+    return new Xex.IntTerm (pos);
   }
 
   function Fmark (domain, vari, args)
@@ -1804,10 +2031,10 @@ MIM.im_domain.DefType (MIM.State.prototype);
 
   function Fpushback (domain, vari, args)
   {
-    var arg = (args[0].IsInt ? args[0].Intval
-              : args[0].IsStr ? new KeySeq (args[0])
-              : args[0]);
-    domain.context.pushback (arg)
+    var a = (args[0].IsInt ? args[0].Intval ()
+            : args[0].IsStr ? new KeySeq (args[0])
+            : args[0]);
+    domain.context.pushback (a);
     return args[0];
   }
 
@@ -1931,7 +2158,7 @@ MIM.im_domain.DefType (MIM.State.prototype);
            if (vari != null)
              this.domain.Defvar (vname);
          }
-       Xex.Term.Parse (this.domain, node);
+       vname = Xex.Term.Parse (this.domain, node)
       }
   }
   parsers['command-list'] = function (node)
@@ -2034,7 +2261,6 @@ MIM.im_domain.DefType (MIM.State.prototype);
            parser.call (this, node);
        }
       this.load_status = MIM.LoadStatus.Loaded;
-      MIM.log ('loading done: ' + this.lang + '-' + this.name + '-' + this.extra_id);
       return true;
     }
   }
@@ -2051,8 +2277,14 @@ MIM.im_domain.DefType (MIM.State.prototype);
     this.target = target;
     this.domain = new Xex.Domain ('context', im.domain, this);
     this.active = true;
+    this.range = new Array ();
+    this.range[0] = this.range[1] = 0;
+    this.state = null;
+    this.initial_state = this.im.initial_state;
+    this.keys = new MIM.KeySeq ();
+    this.marker_positions = new Array ();
+    this.candidate_table = new MIM.CandidateTable ();
     this.reset ();
-    this.spot = 0;
   }
 
   MIM.CandidateTable = function ()
@@ -2060,12 +2292,12 @@ MIM.im_domain.DefType (MIM.State.prototype);
     this.table = new Array ();
   }
 
-  MIM.CandidateTable.prototype.get = function (from)
+  MIM.CandidateTable.prototype.get = function (pos)
   {
     for (var i = 0; i < this.table.length; i++)
       {
        var elt = this.table[i];
-       if (elt.from <= from && elt.to > from)
+       if (elt.from < pos && pos <= elt.to)
          return elt.val;
       }
   }
@@ -2075,8 +2307,7 @@ MIM.im_domain.DefType (MIM.State.prototype);
     for (var i = 0; i < this.table.length; i++)
       {
        var elt = this.table[i];
-       if (elt.from >= from && elt.from < to
-           || elt.to >= from && elt.to < to)
+       if (elt.from < to && elt.to > from)
          {
            elt.from = from;
            elt.to = to;
@@ -2090,6 +2321,8 @@ MIM.im_domain.DefType (MIM.State.prototype);
   MIM.CandidateTable.prototype.adjust = function (from, to, inserted)
   {
     var diff = inserted - (to - from);
+    if (diff == 0)
+      return;
     for (var i = 0; i < this.table.length; i++)
       {
        var elt = this.table[i];
@@ -2106,265 +2339,6 @@ MIM.im_domain.DefType (MIM.State.prototype);
     this.table.length = 0;
   }
 
-  function Block (index, term)
-  {
-    this.Index = index;
-    if (term.IsStr)
-      this.Data = term.val;
-    else if (term.IsList)
-      {
-       this.Data = new Array ();
-       for (var i = 0; i < term.val.length; i++)
-         this.Data.push (term.val[i].val);
-      }
-  }
-
-  Block.prototype.Count = function () { return this.Data.length; }
-  Block.prototype.get = function (i)
-  {
-    return (this.Data instanceof Array ? this.Data[i] : this.Data.charAt (i));
-  }
-
-  function fill_group (start)
-  {
-    var nitems = this.group.length;
-    var r = this.row;
-    var b = this.blocks[r];
-
-    if (start < b.Index)
-      while (start < b.Index)
-       b = this.blocks[--r];
-    else
-      while (start >= b.Index + b.Count ())
-       b = this.blocks[++r];
-    this.row = r;
-
-    var count = b.Count ();
-    start -= b.Index;
-    for (var i = 0; i < nitems; i++, start++)
-      {
-       if (start >= count)
-         {
-           r++;
-           if (r == this.blocks.Length)
-             return i;
-           b = this.blocks[r];
-           count = b.Count ();
-           start = 0;
-         }
-       this.group[i] = b.get (start);
-      }
-    return nitems;
-  }
-
-  function Candidates (candidates, column)
-  {
-    this.column = column;
-    this.row = 0;
-    this.index = 0;
-    this.total = 0;
-    this.blocks = new Array ();
-
-    for (var i = 0; i < candidates.length; i++)
-      {
-       var block = new Block (this.total, candidates[i]);
-       this.blocks.push (block);
-       this.total += block.Count ();
-      }
-  }
-
-  Candidates.prototype.Column = function ()
-  {
-    return (this.column > 0 ? this.index % this.column
-           : this.index - this.blocks[this.row].Index);
-  }
-
-  Candidates.prototype.GroupLength = function ()
-  {
-    if (this.column > 0)
-      {
-       var start = this.index - (this.index % this.column);
-       return (start + this.column <= this.total ? this.column
-               : this.total - start);
-      }
-    return this.blocks[this.row].Count;
-  }
-
-  Candidates.prototype.Current = function ()
-  {
-    var b = this.blocks[this.row];
-    return b.get (this.index - b.Index);
-  }
-
-  Candidates.prototype.PrevGroup = function ()
-  {
-    var col = this.Column ();
-    var nitems;
-    if (this.column > 0)
-      {
-       this.index -= this.column;
-       if (this.index >= 0)
-         nitems = this.column;
-       else
-         {
-           var lastcol = (this.total - 1) % this.column;
-           this.index = (col < lastcol ? this.total - lastcol + col
-                         : this.total - 1);
-           this.row = this.blocks.length - 1;
-           nitems = lastcol + 1;
-         }
-       while (this.blocks[this.row].Index > this.index)
-         this.row--;
-      }
-    else
-      {
-       this.row = this.row > 0 ? this.row - 1 : this.blocks.length - 1;
-       nitems = this.blocks[this.row].Count ();
-       this.index = (this.blocks[this.row].Index
-                     + (col < nitems ? col : nitems - 1));
-      }
-    return nitems;
-  }
-
-  Candidates.prototype.NextGroup = function ()
-  {
-    var col = this.Column ();
-    var nitems;
-    if (this.column > 0)
-      {
-       this.index += this.column - col;
-       if (this.index < this.total)
-         {
-           if (this.index + col >= this.total)
-             {
-               nitems = this.total - this.index;
-               this.index = this.total - 1;
-             }
-           else
-             {
-               nitems = this.column;
-               this.index += col;
-             }
-         }
-       else
-         {
-           this.index = col;
-           this.row = 0;
-         }
-       while (this.blocks[this.row].Index > this.index)
-         this.row++;
-      }
-    else
-      {
-       this.row = this.row < this.blocks.length - 1 ? this.row + 1 : 0;
-       nitems = this.blocks[this.row].Count ();
-       this.index = (this.blocks[this.row].Index
-                     + (col < nitems ? col : nitems - 1));
-      }
-    return nitems;
-  }
-
-  Candidates.prototype.Prev = function ()
-  {
-    if (this.index == 0)
-      {
-       this.index = this.total - 1;
-       this.row = this.blocks.length - 1;
-      }
-    else
-      {
-       this.index--;
-       if (this.blocks[this.row].Index > this.index)
-         this.row--;
-      }
-    }
-
-  Candidates.prototype.Next = function ()
-  {
-    this.index++;
-    if (this.index == this.total)
-      {
-       this.index = 0;
-       this.row = 0;
-      }
-    else
-      {
-       var b = this.blocks[this.row];
-       if (this.index == b.Index + b.Count ())
-         this.row++;
-      }
-  }
-
-  Candidates.prototype.First = function ()
-  {
-    this.index -= this.Column ();
-    while (this.blocks[this.row].Index > this.index)
-      this.row--;
-  }
-
-  Candidates.prototype.Last = function ()
-  {
-    var b = this.blocks[this.row];
-    if (this.column > 0)
-      {
-       if (this.index + 1 < this.total)
-         {
-           this.index += this.column - this.Column () + 1;
-           while (b.Index + b.Count () <= this.index)
-             b = this.blocks[++this.row];
-         }
-      }
-    else
-      this.index = b.Index + b.Count () - 1;
-  }
-
-  Candidates.prototype.Select = function (selector)
-  {
-    if (selector.type == 'selector')
-      {
-       switch (selector.val)
-         {
-         case '@<': this.First (); break;
-         case '@>': this.Last (); break;
-         case '@-': this.Prev (); break;
-         case '@+': this.Next (); break;
-         case '@[': this.PrevGroup (); break;
-         case '@]': this.NextGroup (); break;
-         default: break;
-         }
-       return this.Current ();
-      }
-    var col, start, end
-    if (this.column > 0)
-      {
-       col = this.index % this.column;
-       start = this.index - col;
-       end = start + this.column;
-      }
-    else
-      {
-       start = this.blocks[this.row].Index;
-       col = this.index - start;
-       end = start + this.blocks[this.row].Count;
-      }
-    if (end > this.total)
-      end = this.total;
-    this.index += selector.val - col;
-    if (this.index >= end)
-      this.index = end - 1;
-    if (this.column > 0)
-      {
-       if (selector.val > col)
-         while (this.blocks[this.row].Index + this.blocks[this.row].Count
-                < this.index)
-           this.row++;
-       else
-         while (this.blocks[this.row].Index > this.index)
-           this.row--;
-      }
-    return this.Current ();
-  }
-
   function detach_candidates (ic)
   {
     ic.candidate_table.clear ();
@@ -2378,10 +2352,7 @@ MIM.im_domain.DefType (MIM.State.prototype);
   function set_cursor (prefix, pos)
   {
     this.cursor_pos = pos;
-    if (pos > 0)
-      this.candidates = this.candidate_table.get (pos - 1);
-    else
-      this.candidates = null;
+    this.candidates = this.candidate_table.get (pos);
   }
 
   function save_state ()
@@ -2403,9 +2374,8 @@ MIM.im_domain.DefType (MIM.State.prototype);
   {
     var out = this.keymap.Lookup (this.keys, this.key_head);
     var sub = out.map;
-    var branch_actions = this.state.keymap.actions;
 
-    MIM.log ('handling ' + this.keys.val[this.key_head]
+    Xex.Log ('handling ' + this.keys.val[this.key_head]
             + ' in ' + this.state.name + ':' + this.keymap.name);
     this.key_head = out.index;
     if (sub != this.keymap)
@@ -2413,29 +2383,29 @@ MIM.im_domain.DefType (MIM.State.prototype);
 
        restore_state.call (this);
        this.keymap = sub;
-       MIM.log ('submap found');
-       if (this.keymap.actions)
+       Xex.Log ('submap found');
+       if (this.keymap.map_actions)
          {
-           MIM.log ('taking map actions:');
-           if (! this.take_actions (this.keymap.actions))
+           Xex.Log ('taking map actions:');
+           if (! this.take_actions (this.keymap.map_actions))
              return false;
          }
        else if (this.keymap.submaps)
          {
-           MIM.log ('no map actions');
+           Xex.Log ('no map actions');
            for (var i = this.state_key_head; i < this.key_head; i++)
              {
-               MIM.log ('inserting key:' + this.keys.val[i].key);
-               this.insert (this.keys.val[i].key, null);
+               Xex.Log ('inserting key:' + this.keys.val[i].key);
+               this.ins (this.keys.val[i].key, null);
              }
          }
        if (! this.keymap.submaps)
          {
-           MIM.log ('terminal:');
+           Xex.Log ('terminal:');
            if (this.keymap.branch_actions != null)
              {
-               MIM.log ('branch actions:');
-               if (! this.take_actions (branch_actions))
+               Xex.Log ('branch actions:');
+               if (! this.take_actions (this.keymap.branch_actions))
                  return false;
              }
            if (this.keymap != this.state.keymap)
@@ -2444,24 +2414,24 @@ MIM.im_domain.DefType (MIM.State.prototype);
       }
     else
       {
-       MIM.log ('no submap');
+       Xex.Log ('no submap');
        var current_state = this.state;
        var map = this.keymap;
 
-       if (branch_actions)
+       if (map.branch_actions)
          {
-           MIM.log ('branch actions');
-           if (! this.take_actions (this.keymap.branch_actions))
+           Xex.Log ('branch actions');
+           if (! this.take_actions (map.branch_actions))
              return false;
          }
 
        if (map == this.keymap)
          {
-           MIM.log ('no state change');
+           Xex.Log ('no state change');
            if (map == this.initial_state.keymap
                && this.key_head < this.keys.val.length)
              {
-               MIM.log ('unhandled');
+               Xex.Log ('unhandled');
                return false;
              }
            if (this.keymap != current_state.keymap)
@@ -2476,24 +2446,16 @@ MIM.im_domain.DefType (MIM.State.prototype);
   proto = {
     reset: function ()
     {
-      this.produced = null;
-      this.preedit = '';
-      this.preedit_saved = '';
       this.cursor_pos = 0;
-      this.marker_positions = {};
-      this.candidates = null;
       this.candidate_show = false;
-      this.state = null;
       this.prev_state = null;
-      this.initial_state = this.im.initial_state;
       this.title = this.initial_state.title;
       this.state_preedit = '';
       this.state_key_head = 0;
       this.state_var_values = {};
       this.state_pos = 0;
-      this.keymap = null;
-      this.keys = new MIM.KeySeq ();
       this.key_head = 0;
+      this.keys.val.length = 0;
       this.key_unhandled = false;
       this.unhandled_key = null;
       this.changed = MIM.ChangedStatus.None;
@@ -2502,10 +2464,11 @@ MIM.im_domain.DefType (MIM.State.prototype);
       this.produced = '';
       this.preedit = '';
       this.preedit_saved = '';
-      this.marker_positions = {};
-      this.candidate_table = new MIM.CandidateTable ();
+      this.candidate_table.clear ();
       this.candidates = null;
       this.candidate_show = false;
+      for (var elt in this.marker_positions)
+       this.marker_positions[elt] = 0;
       this.shift (this.initial_state);
     },
 
@@ -2523,19 +2486,66 @@ MIM.im_domain.DefType (MIM.State.prototype);
 
     GetSurroundingChar: function (pos)
     {
-      if (pos < 0 ? this.caret_pos < - pos : this.target.value.length < pos)
-       return 0;
-      return this.target.value.charCodeAt (this.caret_pos + pos);
+      if (pos < 0)
+       {
+         pos += this.range[0];
+         if (pos < 0)
+           return 0;
+       }
+      else
+       {
+         pos += this.range[1];
+         if (pos >= this.target.value.length)
+           return 0;
+       }
+      return this.target.value.charCodeAt (pos);
     },
     
+    DelSurroundText: function (pos)
+    {
+      var text;
+      if (pos < 0)
+       {
+         pos += this.range[0];
+         if (pos <= 0)
+           {
+             pos = 0; text = '';
+           }
+         else
+           text = this.target.value.substring (0, pos);
+         if (this.range[0] < this.target.value.length)
+           text += this.target.value.substring (this.range[0]);
+         this.target.value = text;
+         this.range[1] -= this.range[0] - pos;
+         this.range[0] = pos;
+       }
+      else
+       {
+         pos += this.range[1];
+         text = this.target.value.substring (0, this.range[1]);
+         if (pos >= this.target.value.length)
+           pos = this.target.value.length;
+         else
+           text += this.target.value.substring (pos);
+         this.target.value = text;
+       }
+    },
+
     adjust_markers: function (from, to, inserted)
     {
       var diff = inserted - (to - from);
 
-      for (var m in this.marker_positions)
-       if (this.marker_positions[m] > from)
-         this.marker_positions[m] = (this.marker_positions[m] >= to
-                                     ? pos + diff : from);
+      for (var name in this.marker_positions)
+       {
+         var pos = this.marker_positions[name];
+         if (pos > from)
+           {
+             if (pos >= to)
+               this.marker_positions[name] += diff;
+             else if (pos > from)
+               this.marker_positions[name] = from;
+           }
+       }
       if (this.cursor_pos >= to)
        set_cursor.call (this, 'adjust', this.cursor_pos + diff);
       else if (this.cursor_pos > from)
@@ -2552,31 +2562,46 @@ MIM.im_domain.DefType (MIM.State.prototype);
        this.candidate_table.put (from, from + text.length, candidates)
     },
 
-    insert: function (text, candidates)
+    ins: function (text, candidates)
     {
       this.preedit_replace (this.cursor_pos, this.cursor_pos, text, candidates);
       this.changed = MIM.ChangedStatus.Preedit | MIM.ChangedStatus.CursorPos;
     },
 
+    rep: function (old_text, new_text, candidates)
+    {
+      this.preedit_replace (this.cursor_pos - old_text.length,
+                           this.cursor_pos, new_text, candidates);
+      this.changed = MIM.ChangedStatus.Preedit | MIM.ChangedStatus.CursorPos;
+    },
+
     del: function (pos)
     {
       var deleted = pos - this.cursor_pos;
-      if (pos < 0)
+      if (pos < this.cursor_pos)
        {
-         this.DelSurroundText (pos);
-         pos = 0;
+         if (pos < 0)
+           {
+             this.DelSurroundText (pos);
+             deleted = - this.cursor_pos;
+             pos = 0;
+           }
+         if (pos < this.cursor_pos)
+           this.preedit_replace (pos, this.cursor_pos, '', null);
        }
-      else if (pos > this.preedit.length)
+      else
        {
-         this.DelSurroundText (pos - this.preedit.length);
-         pos = this.preedit.length;
+         if (pos > this.preedit.length)
+           {
+             this.DelSurroundText (pos - this.preedit.length);
+             deleted = this.preedit.length - this.cursor_pos;
+             pos = this.preedit.length;
+           }
+         if (pos > this.cursor_pos)
+           this.preedit_replace (this.cursor_pos, pos, '', null);
        }
-      if  (pos < this.cursor_pos)
-       this.preedit = (this.predit.substring (0, pos)
-                       + this.preedit.substring (this.cursor_pos));
-      else
-       this.preedit = (this.preedit.substring (0, this.cursor_pos)
-                       + this.predit.substring (pos));
+      if (deleted != 0)
+       this.changed = MIM.ChangedStatus.Preedit | MIM.ChangedStatus.CursorPos;
       return deleted;
     },
 
@@ -2654,13 +2679,10 @@ MIM.im_domain.DefType (MIM.State.prototype);
     {
       if (state == null)
         {
-         MIM.log ("shifting back to previous");
          if (this.prev_state == null)
            return;
          state = this.prev_state;
        }
-      else
-       MIM.log ("shifting to " + state.name);
 
       if (state == this.initial_state)
         {
@@ -2835,12 +2857,6 @@ MIM.add_event_listener
         = function (e) { listener.call (target, e || window.event); };
      });
 
-MIM.log = function (msg)
-{
-  var node = document.getElementById ('log');
-  node.value = msg + "\n" + node.value;
-}
-
 MIM.debug_print = function (event, ic)
 {
   if (! MIM.debug)
@@ -2871,14 +2887,22 @@ MIM.debug_print = function (event, ic)
     MIM.debug_nodes['status' + index].innerHTML = 'no IM';
   MIM.debug_nodes['keymap' + index].innerHTML = ic.state.name;
   MIM.debug_nodes['preedit' + index].innerHTML = ic.preedit;
+  if (index == 0)
+    {
+      MIM.debug_nodes.keypress.innerHTML = '';
+      MIM.debug_nodes.status1.innerHTML = '';
+      MIM.debug_nodes.keymap1.innerHTML = '';
+      MIM.debug_nodes.preedit1.innerHTML = ''
+    }
 };
 
-MIM.get_range = function (target, range)
+MIM.get_range = function (target, ic)
 {
+  var from, to;
   if (target.selectionStart != null) // for Mozilla
     {
-      range[0] = target.selectionStart;
-      range[1] = target.selectionEnd;
+      from = target.selectionStart;
+      to = target.selectionEnd;
     }
   else                         // for IE
     {
@@ -2887,9 +2911,15 @@ MIM.get_range = function (target, range)
 
       rr.moveToElementText (target);
       rr.setEndPoint ('EndToEnd', range);
-      range[0] = rr.text.length - r.text.length;
-      range[1] = rr.text.length;
+      from = rr.text.length - r.text.length;
+      to = rr.text.length;
     }
+  if (ic.range[0] == from && ic.range[1] == to
+      && (to == from || target.value.substring (from, to) == ic.preedit))
+    return true;
+  ic.range[0] = from;
+  ic.range[1] = to;
+  return false;
 }
 
 MIM.set_caret = function (target, ic)
@@ -2897,45 +2927,27 @@ MIM.set_caret = function (target, ic)
   if (target.setSelectionRange)        // Mozilla
     {
       var scrollTop = target.scrollTop;
-      target.setSelectionRange (ic.spot, ic.spot + ic.preedit.length);
+      target.setSelectionRange (ic.range[0], ic.range[1]);
       target.scrollTop = scrollTop;
     }
   else                         // IE
     {
       var range = target.createTextRange ();
-      range.moveStart ('character', ic.spot);
-      range.moveEnd ('character', ic.spot + ic.preedit.length);
+      range.moveStart ('character', ic.range[0]);
+      range.moveEnd ('character', ic.range[1]);
       range.select ();
     }
 };
 
-(function () {
-  var range = new Array ();
-
-  MIM.check_range = function (target, ic)
-  {
-    MIM.get_range (target, range);
-    if (range[0] != ic.spot || range[1] - range[0] != ic.preedit.length
-       || target.value.substring (range[0], range[1]) != ic.preedit)
-      {
-       MIM.log ('reset:' + ic.spot + '-' + (ic.spot + ic.preedit.length)
-                + '/' + range[0] + '-' + range[1]);
-       ic.reset ();
-      }
-    target.value = (target.value.substring (0, range[0])
-                   + target.value.substring (range[1]));
-    ic.spot = range[0];
-  }
-}) ();
-
 MIM.update = function (target, ic)
 {
   var text = target.value;
-  target.value = (text.substring (0, ic.spot)
+  target.value = (text.substring (0, ic.range[0])
                  + ic.produced
                  + ic.preedit
-                 + text.substring (ic.spot));
-  ic.spot += ic.produced.length;
+                 + text.substring (ic.range[1]));
+  ic.range[0] += ic.produced.length;
+  ic.range[1] = ic.range[0] + ic.preedit.length;
   MIM.set_caret (target, ic);
 };
 
@@ -2943,41 +2955,52 @@ MIM.reset_ic = function (event)
 {
   if (event.target.mim_ic)
     {
-      var ic = event.target.mim_ic;
-      var pos = ic.spot + ic.preedit.length;
+      var target = event.target;
+      var ic = target.mim_ic;
+      if (ic.preedit.length > 0)
+       event.target.setSelectionRange (ic.range[1], ic.range[1]);
       ic.reset ();
-      if (pos > ic.spot)
-       event.target.setSelectionRange (pos, pos);
     }
 };
 
 MIM.keydown = function (event)
 {
   var target = event.target;
+  if (target.id == 'log')
+    return;
   if (! (target.type == "text" || target.type == "textarea"))
     return;
 
   var ic = target.mim_ic;
   if (! ic || ic.im != MIM.current)
     {
-      MIM.log ('creating IC');
+      target.mim_ic = null;
+      Xex.Log ('creating IC');
       ic = new MIM.IC (MIM.current, target);
+      if (ic.im.load_status != MIM.LoadStatus.Loaded)
+       return;
       target.mim_ic = ic;
       MIM.add_event_listener (target, 'blur', MIM.reset_ic);
+      MIM.get_range (target, ic)
+    }
+  else
+    {
+      if (! MIM.get_range (target, ic))
+       ic.reset ();
     }
-  if (ic.im.load_status != MIM.LoadStatus.Loaded)
-    return;
-  MIM.check_range (target, ic);
   MIM.debug_print (event, ic);
   ic.key = MIM.decode_key_event (event);
 };
 
 MIM.keypress = function (event)
 {
-  if (! (event.target.type == "text" || event.target.type == "textarea"))
+  var target = event.target;
+  if (target.id == 'log')
+    return;
+  if (! (target.type == "text" || target.type == "textarea"))
     return;
 
-  var ic = event.target.mim_ic;
+  var ic = target.mim_ic;
   var i;
 
   try {
@@ -2991,9 +3014,9 @@ MIM.keypress = function (event)
        return;
       }
     
-    MIM.log ("filtering " + ic.key);
+    Xex.Log ("filtering " + ic.key);
     var result = ic.Filter (ic.key);
-    MIM.update (event.target, ic);
+    MIM.update (target, ic);
     if (! ic.key_unhandled)
       event.preventDefault ();
   } finally {
@@ -3002,10 +3025,89 @@ MIM.keypress = function (event)
   return;
 };
 
+MIM.Lang = {
+  European: new Array ('de', 'fr'),
+  MiddleEast: new Array ('ar', 'he'),
+  SouthAsia: new Array ('hi'),
+  SouthEastAsia: new Array ('th'),
+  EastAsia: new Array ('ja', 'zh'),
+  Other: new Array ()
+};
+
+// Other
+// am
+// ath
+// bla
+// cr
+// el
+// eo
+// iu
+// nsk
+// oj
+
+// Middle Eastern
+// ar
+// dv
+// fa
+// he
+// hi
+// kk
+// ps
+// ug
+// yi*
+
+// South Asian
+// as
+// bn
+// bo
+// gu
+// kn
+// ks
+// ml
+// mr
+// ne
+// or
+// pa
+// sd
+// sa
+// si
+// ta
+// te
+// ur
+
+// European
+// cs
+// da
+// eo
+// fr
+// hr
+// hy
+// kk
+// ru
+// sk
+// sr
+// sv
+// vi* 
+// yi*
+
+// East Asian
+// ii
+// ja
+// ko
+// zh
+
+// SouthEast Asian
+// km
+// lo
+// my
+// tai
+// th
+// vi*
+
 MIM.select_im = function (event)
 {
   var target = event.target.parentNode;
-  while (target.tagName != "SELECT")
+  while (target.tagName != "mim-select-im")
     target = target.parentNode;
   var idx = 0;
   var im = false;
@@ -3021,48 +3123,127 @@ MIM.select_im = function (event)
   if (im && im != MIM.current)
     {
       MIM.current = im;
-      MIM.log ('select IM: ' + im.name);
+      Xex.Log ('select IM: ' + im.name);
     }
 };
 
 MIM.destroy_menu = function (event)
 {
-  if (event.target.tagName == "SELECT")
+  if (event.target.tagName == "mim-select-im")
     document.getElementsByTagName ('body')[0].removeChild (event.target);
 };
 
 MIM.select_menu = function (event)
 {
   var target = event.target;
+  var sel;
 
   if (! ((target.type == "text" || target.type == "textarea")
         && event.which == 1 && event.ctrlKey))
     return;
-
-  var sel = document.createElement ('select');
-  sel.onclick = MIM.select_im;
-  sel.onmouseout = MIM.destroy_menu;
-  sel.style.position='absolute';
-  sel.style.left = (event.clientX - 10) + "px";
-  sel.style.top = (event.clientY - 10) + "px";
-  sel.target = target;
-  var idx = 0;
-  for (var lang in MIM.imlist)
-    for (var name in MIM.imlist[lang])
-      {
-       var option = document.createElement ('option');
-       var imname = lang + "-" + name;
-       option.appendChild (document.createTextNode (imname));
-       option.value = imname;
-       sel.appendChild (option);
-       if (MIM.imlist[lang][name] == MIM.current)
-         sel.selectedIndex = idx;
-       idx++;
-      }
-  sel.size = idx;
+  if (! sel)
+    {
+      sel = document.createElement ('select');
+      sel.onclick = MIM.select_im;
+      sel.onmouseout = MIM.destroy_menu;
+      sel.style.position='absolute';
+      sel.style.left = (event.clientX - 10) + "px";
+      sel.style.top = (event.clientY - 10) + "px";
+      sel.target = target;
+      var idx = 0;
+      for (var lang in MIM.imlist)
+       for (var name in MIM.imlist[lang])
+         {
+           var option = document.createElement ('option');
+           var imname = lang + "-" + name;
+           option.appendChild (document.createTextNode (imname));
+           option.value = imname;
+           sel.appendChild (option);
+           if (MIM.imlist[lang][name] == MIM.current)
+             sel.selectedIndex = idx;
+           idx++;
+         }
+      sel.size = idx;
+    }
   document.getElementsByTagName ('body')[0].appendChild (sel);
 };
 
+MIM.select_menu = function (event)
+{
+  var target = event.target;
+  var menu;
+
+  if (! ((target.type == "text" || target.type == "textarea")
+        && event.which == 1 && event.ctrlKey))
+    return;
+  if (! menu)
+    {
+      menu = document.createElement ('ul');
+      menu.style.margin = '0';
+      menu.style.padding = '0';
+      menu.id = 'mim-select-im';
+      menu.onclick = MIM.select_im;
+      menu.onmouseout = MIM.destroy_menu;
+      menu.style.position='absolute';
+      menu.style.left = (event.clientX - 10) + "px";
+      menu.style.top = (event.clientY - 10) + "px";
+      menu.style['border-bottom'] = '1px solid #ccc';
+      menu.style['background-color'] = 'white';
+      menu.target = target;
+      var idx = 0;
+      for (var lang in MIM.imlist)
+       {
+         var li = document.createElement ('li');
+         li.style.position = 'relative';
+         li.style['list-style']= 'none';
+         li.style.margin = '0';
+         li.style.padding = '0';
+         li.onmouseover = function ()
+         {
+           this.style.backgroundColor = 'yellow';
+           var children = this.getElementsByTagName ('ul');
+           for (var i = children.length - 1; i >= 0; i--)
+             {
+               children[i].display = 'block';
+               children[i].visibility = 'visible';
+               children[i].left = '100px';
+             }
+           children = this.getElementsByTagName ('li');
+           for (var i = children.length - 1; i >= 0; i--)
+             {
+               children[i].display = 'block';
+               children[i].visibility = 'visible';
+               children[i].left = '100px';
+             }
+         };
+         li.onmouseout = function () { this.style.backgroundColor = 'white'; };
+         li.appendChild (document.createTextNode (lang));
+         var sub = document.createElement ('ul');
+         sub.style.position = 'absolute';
+         sub.style.top = '0px';
+         sub.style.visibility = 'hidden';
+         li.appendChild (sub);
+         for (var name in MIM.imlist[lang])
+           {
+             var sub_li = document.createElement ('li');
+             var imname = '  ' + lang + "-" + name;
+             sub_li.style.position = 'absolute';
+             sub_li.style.top = '0px';
+             sub_li.style['list-style']= 'none';
+             //sub_li.style.visibility = 'hidden';
+             sub_li.appendChild (document.createTextNode (imname));
+             sub.appendChild (sub_li);
+             if (MIM.imlist[lang][name] == MIM.current)
+               menu.selectedIndex = idx;
+             idx++;
+           }
+         menu.appendChild (li);
+       }
+      menu.size = idx;
+    }
+  document.getElementsByTagName ('body')[0].appendChild (menu);
+};
+
 MIM.test = function ()
 {
   var im = MIM.imlist['t']['latn-post'];
@@ -3099,5 +3280,6 @@ MIM.init = function ()
 MIM.init_debug = function ()
 {
   MIM.debug = true;
+  Xex.LogNode = document.getElementById ('log');
   MIM.init ();
 };