*** empty log message ***
[m17n/m17n-lib-js.git] / xex.js
diff --git a/xex.js b/xex.js
index f3d5088..c9199a0 100644 (file)
--- a/xex.js
+++ b/xex.js
@@ -2,14 +2,20 @@
 
 var Xex = {
   LogNode: null,
-  Log: function (indent, arg)
+  Log: function (arg, indent)
   {
     if (! Xex.LogNode)
       return;
-    var str = '';
-    for (var i = 0; i < indent; i++)
-      str += '  ';
-    Xex.LogNode.value = str + arg + "\n" + Xex.LogNode.value;
+    if (! arg)
+      Xex.LogNode.value = '';
+    else
+      {
+       var str = '';
+       if (indent != undefined)
+         for (var i = 0; i <= indent; i++)
+           str += '  ';
+       Xex.LogNode.value = str + arg + "\n" + Xex.LogNode.value;
+      }
   }
 };
 
@@ -85,7 +91,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);
@@ -182,9 +188,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;
        }
@@ -620,7 +626,7 @@ Xex.Varref = function (vname)
   {
     if (! this.vari || this.vari.domain != domain)
       this.vari = domain.GetVarCreate (this.val);
-    Xex.Log (domain.depth, this.ToString () + '=>' + this.vari.val);
+    Xex.Log (this.ToString () + '=>' + this.vari.val, domain.depth);
     return this.vari.val;
   }
 
@@ -676,12 +682,14 @@ Xex.Funcall = function (func, vari, args)
 
   proto.Eval = function (domain)
   {
-    Xex.Log (domain.depth++, this);
+    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 (--domain.depth, this + ' => ' + result);
+      Xex.Log (this + ' => ' + result, --domain.depth);
     }
     return result;
   }
@@ -708,8 +716,12 @@ Xex.Funcall = function (func, vari, args)
       str += ' vname="' + this.vari.name + '"';
     if (len == 0)
       return str + '/>';
-    for (var i = 0; i < len; i++)
-      arglist += this.args[i].toString ();
+    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 + '>';
   }
 
@@ -852,19 +864,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');
@@ -874,7 +873,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;
@@ -1069,9 +1068,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++)
              {
@@ -1306,6 +1305,8 @@ var MIM = {
   keysyms["del"] = "delete";
 
   function decode_keysym (str) {
+    if (str.length == 1)
+      return str;
     var parts = str.split ("-");
     var len = parts.length, i;
     var has_modifier = len > 1;
@@ -1443,17 +1444,11 @@ var MIM = {
     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 ();
@@ -1538,7 +1533,7 @@ var MIM = {
        throw new Xex.ErrTerm (MIM.Error.ParseError,
                               "Invalid marker: " + name);
       }
-    return new MIM.NamedMarker (name);
+    return new MIM.FloatingMarker (name);;
   }
 }) ();
 
@@ -1653,13 +1648,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;
@@ -1680,16 +1674,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)
   {
@@ -1731,10 +1726,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')
@@ -1988,7 +1981,8 @@ 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)
@@ -1996,7 +1990,7 @@ MIM.im_domain.DefType (MIM.State.prototype);
     var ic = domain.context;
     var gsize = domain.variables['candidates_group_size'];
     var candidates = new MIM.Candidates (args, gsize ? gsize.Intval : 0);
-    ic.insert (candidates.Current (), candidates);
+    ic.ins (candidates.Current (), candidates);
     return args[0];
   }
 
@@ -2004,8 +1998,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)
@@ -2015,12 +2008,12 @@ 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];
   }
 
@@ -2034,7 +2027,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)
@@ -2045,10 +2038,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];
   }
 
@@ -2291,8 +2284,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 ()
@@ -2300,12 +2299,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;
       }
   }
@@ -2315,8 +2314,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;
@@ -2330,6 +2328,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];
@@ -2359,10 +2359,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 ()
@@ -2384,21 +2381,20 @@ 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;
 
     Xex.Log ('handling ' + this.keys.val[this.key_head]
-            + ' in ' + this.state.name + ':' + this.keymap.name + "\n");
+            + ' in ' + this.state.name + ':' + this.keymap.name);
     this.key_head = out.index;
     if (sub != this.keymap)
       {
 
        restore_state.call (this);
        this.keymap = sub;
-       Xex.Log ('submap found\n');
-       if (this.keymap.actions)
+       Xex.Log ('submap found');
+       if (this.keymap.map_actions)
          {
-           Xex.Log ('taking map actions:\n');
-           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)
@@ -2407,7 +2403,7 @@ MIM.im_domain.DefType (MIM.State.prototype);
            for (var i = this.state_key_head; i < this.key_head; i++)
              {
                Xex.Log ('inserting key:' + this.keys.val[i].key);
-               this.insert (this.keys.val[i].key, null);
+               this.ins (this.keys.val[i].key, null);
              }
          }
        if (! this.keymap.submaps)
@@ -2416,7 +2412,7 @@ MIM.im_domain.DefType (MIM.State.prototype);
            if (this.keymap.branch_actions != null)
              {
                Xex.Log ('branch actions:');
-               if (! this.take_actions (branch_actions))
+               if (! this.take_actions (this.keymap.branch_actions))
                  return false;
              }
            if (this.keymap != this.state.keymap)
@@ -2429,10 +2425,10 @@ MIM.im_domain.DefType (MIM.State.prototype);
        var current_state = this.state;
        var map = this.keymap;
 
-       if (branch_actions)
+       if (map.branch_actions)
          {
            Xex.Log ('branch actions');
-           if (! this.take_actions (this.keymap.branch_actions))
+           if (! this.take_actions (map.branch_actions))
              return false;
          }
 
@@ -2457,24 +2453,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;
@@ -2483,10 +2471,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);
     },
 
@@ -2504,21 +2493,66 @@ MIM.im_domain.DefType (MIM.State.prototype);
 
     GetSurroundingChar: function (pos)
     {
-      pos += this.spot;
-      Xex.Log (0, 'getting char at ' + pos);
-      if (pos < 0 || pos >= this.target.value.length)
-       return 0;
+      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)
@@ -2535,31 +2569,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;
     },
 
@@ -2637,13 +2686,10 @@ MIM.im_domain.DefType (MIM.State.prototype);
     {
       if (state == null)
         {
-         Xex.Log ("shifting back to previous");
          if (this.prev_state == null)
            return;
          state = this.prev_state;
        }
-      else
-       Xex.Log ("shifting to " + state.name);
 
       if (state == this.initial_state)
         {
@@ -2779,22 +2825,64 @@ MIM.im_domain.DefType (MIM.State.prototype);
   keys[0x90] = "numlock";
   keys[0xF0] = "capslock";
 
+  var keyids = {};
+  keyids['U+0008'] = 'backspace';
+  keyids['U+0009'] = 'tab';
+  keyids['U+0018'] = 'cancel';
+  keyids['U+001B'] = 'escape';
+  keyids['U+0020'] = 'space';
+  keyids['U+007F'] = 'delete';
+
+  var modifiers = {}
+  modifiers.Shift = 1;
+  modifiers.Control = 1;
+  modifiers.Alt = 1;
+  modifiers.AltGraph = 1;
+  modifiers.Meta = 1
+
   MIM.decode_key_event = function (event)
   {
-    var key = ((event.type == 'keydown' || event.keyCode) ? event.keyCode
+    var key = event.keyIdentifier;
+
+    if (key)                   // keydown event of Chrome
+      {
+       if (modifiers[key])
+         return false;
+       var mod = '';
+       if (event.ctrlKey) mod += 'C-';
+       if (event.metaKey) mod += 'M-';
+       if (event.altKey) mod += 'A-';
+       var keysym = keyids[key];
+       if (keysym)
+         key = keysym;
+       else if (key.match(/^U\+([0-9A-Z]+)$/))
+         {
+           if (mod.length == 0)
+             return;
+           key = String.fromCharCode (parseInt (RegExp.$1, 16));
+         }
+       else
+         key = key.toLowerCase ();
+       if (event.shiftKey) mod += 'S-';
+       return new MIM.Key (mod + key);
+      }
+    else
+      {
+       key = ((event.type == 'keydown' || event.keyCode) ? event.keyCode
               : event.charCode ? event.charCode
               : false);
-    if (! key)
-      return false;
-    if (event.type == 'keydown')
-      {
-       key = keys[key];
        if (! key)
          return false;
-       if (event.shiftKey) key = "S-" + key ;
+       if (event.type == 'keydown')
+         {
+           key = keys[key];
+           if (! key)
+             return false;
+           if (event.shiftKey) key = "S-" + key ;
+         }
+       else
+         key = String.fromCharCode (key);
       }
-    else
-      key = String.fromCharCode (key);
     if (event.altKey) key = "A-" + key ;
     if (event.ctrlKey) key = "C-" + key ;
     return new MIM.Key (key);
@@ -2825,10 +2913,10 @@ MIM.debug_print = function (event, ic)
   if (! MIM.debug_nodes)
     {
       MIM.debug_nodes = new Array ();
-      MIM.debug_nodes['keydown'] = document.getElementById ('keydown');
-      MIM.debug_nodes['keypress'] = document.getElementById ('keypress');
       MIM.debug_nodes['status0'] = document.getElementById ('status0');
       MIM.debug_nodes['status1'] = document.getElementById ('status1');
+      MIM.debug_nodes['keydown'] = document.getElementById ('keydown');
+      MIM.debug_nodes['keypress'] = document.getElementById ('keypress');
       MIM.debug_nodes['keymap0'] = document.getElementById ('keymap0');
       MIM.debug_nodes['keymap1'] = document.getElementById ('keymap1');
       MIM.debug_nodes['preedit0'] = document.getElementById ('preedit0');
@@ -2836,11 +2924,11 @@ MIM.debug_print = function (event, ic)
     }
   var target = event.target;
   var code = event.keyCode;
-  var ch = event.type == 'keydown' ? 0 : event.charCode;
+  var ch = event.type == 'keypress' ? event.charCode : 0;
   var key = MIM.decode_key_event (event);
   var index;
 
-  MIM.debug_nodes[event.type].innerHTML = "" + code + "/" + ch + " : " + key;
+  MIM.debug_nodes[event.type].innerHTML = "" + code + "/" + ch + ":" + key + '/' + event.keyIdentifier;
   index = (event.type == 'keydown' ? '0' : '1');
   if (ic)
     MIM.debug_nodes['status' + index].innerHTML = ic.im.load_status;
@@ -2848,14 +2936,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
     {
@@ -2864,9 +2960,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)
@@ -2874,45 +2976,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)
-      {
-       Xex.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);
 };
 
@@ -2920,41 +3004,60 @@ 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)
     {
+      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);
+  if (ic.key)
+    {
+      Xex.Log ("filtering " + ic.key);
+      var result = ic.Filter (ic.key);
+      MIM.update (target, ic);
+      if (! ic.key_unhandled)
+       event.preventDefault ();
+    }
 };
 
 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 {
@@ -2970,75 +3073,274 @@ MIM.keypress = function (event)
     
     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 ();
+  } catch (e) {
+    Xex.Log ("error:" + e);
+    event.preventDefault ();
   } finally {
     MIM.debug_print (event, ic);
   }
   return;
 };
 
-MIM.select_im = function (event)
-{
-  var target = event.target.parentNode;
-  while (target.tagName != "SELECT")
-    target = target.parentNode;
-  var idx = 0;
-  var im = false;
-  for (var lang in MIM.imlist)
-    for (var name in MIM.imlist[lang])
-      if (idx++ == target.selectedIndex)
-       {
-         im = MIM.imlist[lang][name];
-         break;
-       }
-  document.getElementsByTagName ('body')[0].removeChild (target);
-  target.target.focus ();
-  if (im && im != MIM.current)
-    {
-      MIM.current = im;
-      Xex.Log ('select IM: ' + im.name);
-    }
-};
+(function () {
+  var lang_category = {
+    European: {
+      cs: { name: 'Czech' },
+      da: { name: 'Danish' },
+      el: { name: 'Greek' },
+      en: { name: 'English' },
+      eo: { name: 'Esperanto' },
+      fr: { name: 'French' },
+      grc: { name: 'ClassicGreek' },
+      hr: { name: 'Croatian' },
+      hy: { name: 'Armenian' },
+      ka: { name: 'Georgian' },
+      kk: { name: 'Kazakh' },
+      ru: { name: 'Russian' },
+      sk: { name: 'Slovak' },
+      sr: { name: 'Serbian' },
+      sv: { name: 'Swedish' },
+      vi: { name: 'Vietnamese' },
+      yi: { name: 'Yiddish' } },
+    MiddleEast: {
+      ar: { name: 'Arabic' },
+      dv: { name: 'Divehi' },
+      fa: { name: 'Persian' },
+      he: { name: 'Hebrew' },
+      kk: { name: 'Kazakh' },
+      ps: { name: 'Pushto' },
+      ug: { name: 'Uighur' },
+      yi: { name: 'Yiddish' } },
+    SouthAsia: {
+      as: { name: 'Assamese' },
+      bn: { name: 'Bengali' },
+      bo: { name: 'Tibetan' },
+      gu: { name: 'Gujarati' },
+      hi: { name: 'Hindi' },
+      kn: { name: 'Kannada' },
+      ks: { name: 'Kashmiri' },
+      ml: { name: 'Malayalam' },
+      mr: { name: 'Marathi' },
+      ne: { name: 'Nepali' },
+      or: { name: 'Oriya' },
+      pa: { name: 'Panjabi' },
+      sa: { name: 'Sanskirit' },
+      sd: { name: 'Sindhi' },
+      si: { name: 'Sinhalese' },
+      ta: { name: 'Tamil' },
+      te: { name: 'Telugu' },
+      ur: { name: 'Urdu' } },
+    SouthEastAsia: {
+      cmc: { name: 'Cham' },
+      km: { name: 'Khmer'},
+      lo: { name: 'Lao' },
+      my: { name: 'Burmese' },
+      tai: { name: 'Tai Viet' },
+      th: { name: 'Thai' },
+      vi: { name: 'Vietanamese' } },
+    EastAsia: {
+      ii: { name: 'Yii' },
+      ja: { name: 'Japanese' },
+      ko: { name: 'Korean' },
+      zh: { name: 'Chinese' } },
+    Other: {
+      am: { name:  'Amharic' },
+      ath: { name: 'Carrier' },
+      bla: { name: 'Blackfoot' },
+      cr: { name: 'Cree' },
+      eo: { name: 'Esperanto' },
+      iu: { name: 'Inuktitut' },
+      nsk: { name: 'Naskapi' },
+      oj: { name: 'Ojibwe' },
+      t: { name: 'Generic' } }
+  };
+
+  function categorize_im ()
+  {
+    var cat, lang, list, name;
+    for (lang in MIM.imlist)
+      {
+       list = null;
+       for (cat in lang_category)
+         if (lang_category[cat][lang])
+           {
+             list = lang_category[cat][lang].list;
+             if (! list)
+               list = lang_category[cat][lang].list = {};
+             break;
+           }
+       if (list)
+         for (name in MIM.imlist[lang])
+           list[name] = MIM.imlist[lang][name];
+       else
+         for (name in MIM.imlist[lang])
+           Xex.Log ('no category ' + lang + '-' + name);
+      }
+  }
 
-MIM.destroy_menu = function (event)
-{
-  if (event.target.tagName == "SELECT")
-    document.getElementsByTagName ('body')[0].removeChild (event.target);
-};
+  var destroy_timer;
+  var last_target;
 
-MIM.select_menu = function (event)
-{
-  var target = event.target;
+  function destroy ()
+  {
+    clearTimeout (destroy_timer);
+    destroy_timer = null;
+    var target = document.getElementById ('mim-menu');
+    if (target)
+      {
+       for (; last_target && last_target.menu_level;
+            last_target = last_target.parentLi)
+         last_target.style.backgroundColor = 'white';
+       var nodes = target.getElementsByTagName ('ul');
+       for (var i = 0; i < nodes.length; i++)
+         nodes[i].style.visibility = 'hidden';
+       document.getElementsByTagName ('body')[0].removeChild (target);
+      }
+  }    
 
-  if (! ((target.type == "text" || target.type == "textarea")
-        && event.which == 1 && event.ctrlKey))
-    return;
+  function destroy_menu () {
+    if (! destroy_timer)
+      destroy_timer = setTimeout (destroy, 1000);
+  }
 
-  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;
-  document.getElementsByTagName ('body')[0].appendChild (sel);
-};
+  function show_submenu (event)
+  {
+    if (destroy_timer)
+      {
+       clearTimeout (destroy_timer);
+       destroy_timer = null;
+      }
+    var target = event.target;
+    if (! target.menu_level)
+      return;
+    if (last_target && target.parentLi != last_target)
+      {
+       last_target.style.backgroundColor = 'white';
+       if (target.menu_level < last_target.menu_level)
+         {
+           last_target = last_target.parentLi;
+           last_target.style.backgroundColor = 'white';
+         }
+       var uls = last_target.getElementsByTagName ('ul');
+       for (var i = 0; i < uls.length; i++)
+         uls[i].style.visibility = 'hidden';
+      }
+    last_target = target;
+    target.style.backgroundColor = 'yellow';
+    if (target.menu_level < 3)
+      {
+       target.lastChild.style.visibility = 'visible';
+       target.lastChild.style.left = target.clientWidth + 'px';
+      }
+    event.preventDefault ();   
+  }
+
+  function select_im (event)
+  {
+    var target = event.target;
+    if (target.im)
+      {
+       MIM.current = target.im;
+       destroy ();
+      }
+    event.preventDefault ();
+  }
+
+  function create_ul (visibility)
+  {
+    var ul = document.createElement ('ul');
+    ul.style.position = 'absolute';
+    ul.style.margin = '0px';
+    ul.style.padding = '0px';
+    ul.style.border = '1px solid gray';
+    ul.style.borderBottom = 'none';
+    ul.style.top = '-1px';
+    ul.style.backgroundColor = 'white';
+    ul.style.visibility = visibility;
+    return ul;
+  }
+
+  function create_li (level, text)
+  {
+    var li = document.createElement ('li');
+    li.style.position = 'relative';
+    li.style.margin = '0px';
+    li.style.padding = '1px';
+    li.style.borderBottom = '1px solid gray';
+    li.style.top = '0px';
+    li.style.listStyle = 'none';
+    li.menu_level = level;
+    li.appendChild (document.createTextNode (text));
+    return li;
+  }
+
+  var menu;
+
+  function create_menu (event)
+  {
+    var target = event.target;
+
+    if (! ((target.type == "text" || target.type == "textarea")
+          && event.which == 1 && event.ctrlKey))
+      return;
+    if (! menu)
+      {
+       categorize_im ();
+       menu = create_ul ('visible');
+       menu.style.fontFamily = 'sans-serif';
+       menu.style.fontWeight = 'bold';
+       menu.id = 'mim-menu';
+       menu.onclick = select_im;
+       menu.onmouseover = show_submenu;
+       menu.onmouseout = destroy_menu;
+       for (var catname in lang_category)
+         {
+           var cat = lang_category[catname];
+           var li = create_li (1, catname);
+           var sub = create_ul ('hidden');
+           for (var langname in cat)
+             {
+               var lang = cat[langname];
+               if (! lang.list)
+                 continue;
+               var sub_li = create_li (2, lang.name);
+               sub_li.parentLi = li;
+               var subsub = create_ul ('hidden');
+               for (var name in lang.list)
+                 {
+                   var im = lang.list[name];
+                   var subsub_li = create_li (3, im.name);
+                   subsub_li.parentLi = sub_li;
+                   subsub_li.im = im;
+                   subsub.appendChild (subsub_li);
+                 }
+               sub_li.appendChild (subsub);
+               sub.appendChild (sub_li);
+             }
+           li.appendChild (sub);
+           menu.appendChild (li);
+         }
+       document.mimmenu = menu;
+       lang_category = null;
+      }
+    menu.style.left = (event.clientX - 10) + "px";
+    menu.style.top = (event.clientY - 10) + "px";
+    document.getElementsByTagName ('body')[0].appendChild (menu);
+  };
+
+  MIM.init = function ()
+  {
+    MIM.add_event_listener (window, 'keydown', MIM.keydown);
+    MIM.add_event_listener (window, 'keypress', MIM.keypress);
+    MIM.add_event_listener (window, 'mousedown', create_menu);
+    if (window.location == 'http://localhost/mim/index.html')
+      MIM.server = 'http://localhost/mim';
+    MIM.current = MIM.imlist['vi']['telex'];
+  };
+}) ();
 
 MIM.test = function ()
 {
@@ -3063,19 +3365,10 @@ MIM.test = function ()
 }
 
 
-MIM.init = function ()
-{
-  MIM.add_event_listener (window, 'keydown', MIM.keydown);
-  MIM.add_event_listener (window, 'keypress', MIM.keypress);
-  MIM.add_event_listener (window, 'mousedown', MIM.select_menu);
-  if (window.location == 'http://localhost/mim/index.html')
-    MIM.server = 'http://localhost/mim';
-  MIM.current = MIM.imlist['vi']['telex'];
-};
-
 MIM.init_debug = function ()
 {
   MIM.debug = true;
   Xex.LogNode = document.getElementById ('log');
+  Xex.Log (null);
   MIM.init ();
 };