*** empty log message ***
[m17n/m17n-lib-js.git] / xex.js
diff --git a/xex.js b/xex.js
index 0024512..157f521 100644 (file)
--- a/xex.js
+++ b/xex.js
@@ -2,35 +2,6 @@
 
 var Xex = {};
 
-Xex.Alist = function ()
-{
-  this.count = 0;
-}
-
-Xex.Alist.prototype.put = function (key, val)
-{
-  this.count++;
-  return (this[key] = val);
-}
-Xex.Alist.prototype.clone = function ()
-{
-  var alist = new Xex.Alist ();
-  for (key in this)
-    alist[key] = this[key];
-  return alist;
-}
-Xex.Alist.prototype.toString = function ()
-{
-  var str = 'alist:';
-  for (key in this)
-    str += '"' + key + '"';
-  return str;
-}
-
-// Xex.alist = new Xex.Alist ();
-// Xex.alist.put ('abc', "ABC");
-// alert (Xex.alist['abc']);
-
 Xex.Error = {
   UnknownError: "unknown-error",
   WrongArgument: "wrong-argument",
@@ -374,6 +345,12 @@ Xex.Domain.prototype = {
   },
   Defvar: function (name)
   {
+    if (name instanceof Xex.Variable)
+      {
+       name = name.Clone (this);
+       this.variables[name.name] = name;
+       return name;
+      }
     var vari = this.variables[name];
     if (vari)
       {
@@ -393,7 +370,7 @@ Xex.Domain.prototype = {
     var func = this.functions[name];
     if (! func)
       throw new Xex.ErrTerm (Xex.Error.UnknownFunction,
-                            "Unknown function: " + this + ':' + name);
+                            "Unknown function: " + name);
     return func;
   },
   CopyFunc: function (domain, name)
@@ -483,10 +460,12 @@ Xex.ParseTermList = function (domain, node)
        if (n.nodeName == 'defun' || n.nodeName == 'defmacro')
          Xex.parse_defun_head (domain, n);
       }
-  var terms = new Array ();
+  var terms = false;
   for (var n = node; n; n = n.nextSibling)
     if (n.nodeType == 1)
       {
+       if (! terms)
+         terms = new Array ();
        if (n.nodeName == 'defun' || n.nodeName == 'defmacro')
          Xex.parse_defun_body (domain, n);
        else if (n.nodeName == 'defvar')
@@ -795,7 +774,7 @@ Xex.LstTerm = function (list) { this.val = list; };
       return result;
     if (result.IsTrue)
       return args[1].Eval (domain);
-    if (args.Length == 2)
+    if (args.length == 2)
       return Zero;
     return args[2].Eval (domain);
   }
@@ -1299,6 +1278,7 @@ MIM.Action.prototype.Run = function (domain)
 
 MIM.Keymap = function ()
 {
+  this.name = 'TOP';
   this.submaps = null;
   this.actions = null;
 };
@@ -1309,12 +1289,14 @@ MIM.Keymap = function ()
   {
     var keyseq = rule.keyseq;
     var len = keyseq.val.length;
+    var name = '';
 
     for (var i = 0; i < len; i++)
       {
        var key = keyseq.val[i];
-       var sub;
+       var sub = false;
 
+       name += key.key;
        if (! keymap.submaps)
          keymap.submaps = {};
        else
@@ -1322,6 +1304,7 @@ MIM.Keymap = function ()
        if (! sub)
          keymap.submaps[key.key] = sub = new MIM.Keymap ();
        keymap = sub;
+       keymap.name = name;
       }
     keymap.actions = rule.actions;
   }
@@ -1405,7 +1388,20 @@ MIM.im_domain.DefType (MIM.State.prototype);
 
   function Finsert (domain, vari, args)
   {
-    domain.context.insert (args[0].val, null);
+    var text;
+    if (args[0].type == 'integer')
+      text = String.fromCharCode (args[0].val);
+    else
+      text = args[0].val;
+    domain.context.insert (text, null);
+  }
+
+  function Finsert_candidates (domain, vari, args)
+  {
+    var ic = domain.context;
+    var candidates = new Candidates (args, column);
+    ic.insert (candidates.Current (), candidates);
+    return args[0];
   }
 
   im_domain.DefSubr (Finsert, "insert", false, 1, 1);
@@ -1417,6 +1413,19 @@ MIM.im_domain.DefType (MIM.State.prototype);
   {
     this.description = node.firstChild.nodeValue;
   }
+  parses['variable-list'] = function (node)
+  {
+    for (node = node.firstChild; node; node = node.nextSibling)
+      {
+       if (node.nodeType != 1)
+         continue;
+       var name = node.attributes['vname'].nodeValue;
+       var vari = get_global_bar (name);
+       if (vari != null)
+         domain.Defvar (name);
+       Xex.Parse (domain, node);
+      }
+  }
   parsers['title'] = function (node)
   {
     this.title = node.firstChild.nodeValue;
@@ -1497,6 +1506,7 @@ MIM.im_domain.DefType (MIM.State.prototype);
     this.domain = new Xex.Domain ('context', im.domain, this);
     this.active = true;
     this.reset ();
+    this.spot = 0;
   }
 
   MIM.CandidateTable = function ()
@@ -1550,6 +1560,274 @@ 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 ()
+  {
+    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 = funciton (selector)
+  {
+    if (selector instanceof MIM.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 - col;
+    if (this.index >= end)
+      this.index = end - 1;
+    if (this.column > 0)
+      {
+       if (selector > 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 ();
+    ic.candidates = null;
+    ic.changed |= (MIM.ChangedStatus.Preedit | MIM.ChangedStatus.CursorPos
+                  | ChangedStatus.CandidateList
+                  | ChangedStatus.CandidateIndex
+                  | ChangedStatus.CandidateShow);
+  }
+
   function set_cursor (prefix, pos)
   {
     this.cursor_pos = pos;
@@ -1578,30 +1856,39 @@ 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;
 
-    alert ('handling ' + this.keys.val[this.key_head]);
+    MIM.log ('handling ' + this.keys.val[this.key_head]
+            + ' in ' + this.state.name + ':' + this.keymap.name);
     this.key_head = out.index;
     if (sub != this.keymap)
       {
+
        restore_state.call (this);
        this.keymap = sub;
-       alert ('submap found, taking map actions:' + sub.actions);
-       if (this.keymap.actions != null)
+       MIM.log ('submap found');
+       if (this.keymap.actions)
          {
+           MIM.log ('taking map actions:');
            if (! this.take_actions (this.keymap.actions))
              return false;
          }
-       else if (this.keymap.submaps != null)
+       else if (this.keymap.submaps)
          {
+           MIM.log ('no map actions');
            for (var i = this.state_key_head; i < this.key_head; i++)
-             this.preedit_replace (this.cursor_pos, this.cursor_pos,
-                                   this.keys.val[i].key, null);
+             {
+               MIM.log ('inserting key:' + this.keys.val[i].key);
+               this.insert (this.keys.val[i].key, null);
+             }
          }
-       if (this.keymap.submaps == null)
+       if (! this.keymap.submaps)
          {
+           MIM.log ('terminal:');
            if (this.keymap.branch_actions != null)
              {
-               if (! this.take_actions (this.keymap.branch_actions))
+               MIM.log ('branch actions:');
+               if (! this.take_actions (branch_actions))
                  return false;
              }
            if (this.keymap != this.state.keymap)
@@ -1610,21 +1897,29 @@ MIM.im_domain.DefType (MIM.State.prototype);
       }
     else
       {
+       MIM.log ('no submap');
        var current_state = this.state;
+       var map = this.keymap;
 
-       if (this.keymap.branch_actions != null)
+       if (branch_actions)
          {
+           MIM.log ('branch actions');
            if (! this.take_actions (this.keymap.branch_actions))
              return false;
          }
-       if (this.state == current_state)
+
+       if (map == this.keymap)
          {
-           if (this.state == this.initial_state
+           MIM.log ('no state change');
+           if (map == this.initial_state.keymap
                && this.key_head < this.keys.val.length)
-             return false;
-           if (this.keymap != this.state.keymap)
-             this.shift (this.state);
-           else if (this.keymap.branch_actions == null)
+             {
+               MIM.log ('unhandled');
+               return false;
+             }
+           if (this.keymap != current_state.keymap)
+             this.shift (current_state);
+           else if (this.keymap.actions == null)
              this.shift (this.initial_state);
          }
       }
@@ -1632,10 +1927,11 @@ MIM.im_domain.DefType (MIM.State.prototype);
   }
 
   proto = {
-    init: function ()
+    reset: function ()
     {
       this.produced = null;
       this.preedit = '';
+      this.preedit_saved = '';
       this.cursor_pos = 0;
       this.marker_positions = {};
       this.candidates = null;
@@ -1656,18 +1952,13 @@ MIM.im_domain.DefType (MIM.State.prototype);
       this.changed = MIM.ChangedStatus.None;
       this.error_message = '';
       this.title = this.initial_state.title;
-      this.produced = null;
+      this.produced = '';
       this.preedit = '';
+      this.preedit_saved = '';
       this.marker_positions = {};
       this.candidate_table = new MIM.CandidateTable ();
       this.candidates = null;
       this.candidate_show = false;
-    },
-
-    reset: function ()
-    {
-      this.init ();
-      this.state_var_values = {};
       this.shift (this.initial_state);
     },
 
@@ -1806,43 +2097,45 @@ MIM.im_domain.DefType (MIM.State.prototype);
       {
        this.candidate_table.clear ();
        this.produced += this.preedit;
-       this.preedit_replace.call (this, 0, this.preedit.Length, '', null);
+       this.preedit_replace.call (this, 0, this.preedit.length, '', null);
       }
     },
 
     shift: function (state)
     {
       if (state == null)
-      {
-       if (this.prev_state == null)
-         return;
-       state = this.prev_state;
-      }
+        {
+         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)
         {
-         this.commit ();
-         this.keys.val.splice (0, this.key_head);
-         this.key_head = 0;
-         if (state != this.state)
+         if (this.state)
            {
-             this.domain.RestoreValues (this.state_initial_var_values);
-             if (state.enter_actions != null)
-               take_actions.call (state.enter_actions);
+             this.commit ();
+             this.keys.val.splice (0, this.key_head);
+             this.key_head = 0;
+             this.prev_state = null;
            }
-         this.prev_state = null;
        }
       else
-        {
-         if (state != this.state && state.enter_actions != null)
-           take_actions.call (state.enter_actions);
-         this.prev_state = this.state;
+       {
+         if (state != this.state)
+           this.prev_state = this.state;
        }
-      save_state.call (this);
+      if (state != this.state && state.enter_actions)
+       take_actions.call (state.enter_actions);
       if (! this.state || this.state.title != state.title)
        this.changed |= MIM.ChangedStatus.StateTitle;
       this.state = state;
       this.keymap = state.keymap;
+      this.state_key_head = this.key_head;
+      save_state.call (this);
     },
 
     Filter: function (key)
@@ -1864,16 +2157,29 @@ MIM.im_domain.DefType (MIM.State.prototype);
        {
          if (! handle_key.call (this))
            {
-             this.unhandled_key = this.keys.val[this.key_head++];
+             if (this.key_head < this.keys.val.length)
+               {
+                 this.unhandled_key = this.keys.val[this.key_head];
+                 this.keys.val.splice (this.key_head, this.key_head + 1);
+               }
              this.key_unhandled = true;
              break;
            }
          if (++count == 10)
-           break;
+           {
+             this.reset ();
+             this.key_unhandled = true;
+             break;
+           }
        }
-      this.keys.val.splice (0, this.key_head);
-      this.key_head = 0;
-      return (! this.key_unhandled && this.produced.length == 0);
+      if (this.key_unhandled)
+       {
+         this.keys.val.length = 0;
+         this.key_head = this.state_key_head = this.commit_key_head = 0;
+       }
+      return (! this.key_unhandled
+             && this.produced.length == 0
+             && this.preedit.length == 0);
     }
   }
 
@@ -1904,35 +2210,304 @@ MIM.im_domain.DefType (MIM.State.prototype);
 }) ();
 
 (function () {
+  var keys = new Array ();
+  keys[0x09] = 'tab';
+  keys[0x08] = 'backspace';
+  keys[0x0D] = 'return';
+  keys[0x1B] = 'escape';
+  keys[0x20] = 'space';
+  keys[0x21] = 'pageup';
+  keys[0x22] = 'pagedown';
+  keys[0x23] = 'end';
+  keys[0x24] = 'home';
+  keys[0x25] = 'left';
+  keys[0x26] = 'up';
+  keys[0x27] = 'right';
+  keys[0x28] = 'down';
+  keys[0x2D] = 'insert';
+  keys[0x2E] = 'delete';
+  for (var i = 1; i <= 12; i++)
+    keys[111 + i] = "f" + i;
+  keys[0x90] = "numlock";
+  keys[0xF0] = "capslock";
+
+  MIM.decode_key_event = function (event)
+  {
+    var 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 ;
+      }
+    else
+      key = String.fromCharCode (key);
+    if (event.altKey) key = "A-" + key ;
+    if (event.ctrlKey) key = "C-" + key ;
+    return new MIM.Key (key);
+  }
+}) ();
 
-  MIMTEST = function (name) { this.nam = name; };
+MIM.add_event_listener
+  = (window.addEventListener
+     ? function (target, type, listener) {
+       target.addEventListener (type, listener, false);
+     }
+     : window.attachEvent
+     ? function (target, type, listener) {
+       target.attachEvent ('on' + type,
+                          function() {
+                            listener.call (target, window.event);
+                          });
+     }
+     : function (target, type, listener) {
+       target['on' + type]
+        = function (e) { listener.call (target, e || window.event); };
+     });
+
+MIM.log = function (msg)
+{
+  var node = document.getElementById ('log');
+  node.value = msg + "\n" + node.value;
+}
 
-  function testshow () { alert (this.nam); };
+MIM.debug_print = function (event, ic)
+{
+  if (! MIM.debug)
+    return;
+  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['keymap0'] = document.getElementById ('keymap0');
+      MIM.debug_nodes['keymap1'] = document.getElementById ('keymap1');
+      MIM.debug_nodes['preedit0'] = document.getElementById ('preedit0');
+      MIM.debug_nodes['preedit1'] = document.getElementById ('preedit1');
+    }
+  var target = event.target;
+  var code = event.keyCode;
+  var ch = event.type == 'keydown' ? 0 : event.charCode;
+  var key = MIM.decode_key_event (event);
+  var index;
+
+  MIM.debug_nodes[event.type].innerHTML = "" + code + "/" + ch + " : " + key;
+  index = (event.type == 'keydown' ? '0' : '1');
+  if (ic)
+    MIM.debug_nodes['status' + index].innerHTML = ic.im.load_status;
+  else
+    MIM.debug_nodes['status' + index].innerHTML = 'no IM';
+  MIM.debug_nodes['keymap' + index].innerHTML = ic.keymap.name;
+  MIM.debug_nodes['preedit' + index].innerHTML = ic.preedit;
+};
 
-  MIMTEST.prototype.show = function () { testshow.call (this); }
-  MIMTEST.prototype.cut = function (from, to) {
-    this.val.splice (from, to -from); return this.val; }
+MIM.get_range = function (target, range)
+{
+  if (target.selectionStart != null) // for Mozilla
+    {
+      range[0] = target.selectionStart;
+      range[1] = target.selectionEnd;
+    }
+  else                         // for IE
+    {
+      var r = document.selection.createRange ();
+      var rr = r.duplicate ();
 
-  MIMTEST2 = function (name) { this.nam = name;
-                              this.val = new Array (1,2,3,4,5);}
+      rr.moveToElementText (target);
+      rr.setEndPoint ('EndToEnd', range);
+      range[0] = rr.text.length - r.text.length;
+      range[1] = rr.text.length;
+    }
+}
 
-  MIMTEST2.prototype = MIMTEST.prototype;
+MIM.set_caret = function (target, ic)
+{
+  if (target.setSelectionRange)        // Mozilla
+    {
+      var scrollTop = target.scrollTop;
+      target.setSelectionRange (ic.spot, ic.spot + ic.preedit.length);
+      target.scrollTop = scrollTop;
+    }
+  else                         // IE
+    {
+      var range = target.createTextRange ();
+      range.moveStart ('character', ic.spot);
+      range.moveEnd ('character', ic.spot + ic.preedit.length);
+      range.select ();
+    }
+};
+
+(function () {
+  var range = new Array ();
 
-  var x = new MIMTEST2 ('test2');
+  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)
+                 + ic.produced
+                 + ic.preedit
+                 + text.substring (ic.spot));
+  ic.spot += ic.produced.length;
+  MIM.set_caret (target, ic);
+};
+
+MIM.reset_ic = function (event)
+{
+  if (event.target.mim_ic)
+    {
+      var ic = event.target.mim_ic;
+      var pos = ic.spot + ic.preedit.length;
+      ic.reset ();
+      if (pos > ic.spot)
+       event.target.setSelectionRange (pos, pos);
+    }
+};
+
+MIM.keydown = function (event)
+{
+  var target = event.target;
+  if (! (target.type == "text" || target.type == "textarea"))
+    return;
+
+  var ic = target.mim_ic;
+  if (! ic || ic.im != MIM.current)
+    {
+      MIM.log ('creating IC');
+      ic = new MIM.IC (MIM.current);
+      target.mim_ic = ic;
+      MIM.add_event_listener (target, 'blur', MIM.reset_ic);
+    }
+  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"))
+    return;
+
+  var ic = event.target.mim_ic;
+  var i;
+
+  try {
+    if (ic.im.load_status != MIM.LoadStatus.Loaded)
+      return;
+    if (! ic.key)
+      ic.key = MIM.decode_key_event (event);
+    if (! ic.key)
+      {
+       ic.reset ();
+       return;
+      }
+    
+    MIM.log ("filtering " + ic.key);
+    var result = ic.Filter (ic.key);
+    MIM.update (event.target, ic);
+    if (! ic.key_unhandled)
+      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.im_list)
+    for (var name in MIM.im_list[lang])
+      if (idx++ == target.selectedIndex)
+       {
+         im = MIM.im_list[lang][name];
+         break;
+       }
+  document.getElementsByTagName ('body')[0].removeChild (target);
+  target.target.focus ();
+  if (im && im != MIM.current)
+    {
+      MIM.current = im;
+      MIM.log ('select IM: ' + im.name);
+    }
+};
+
+MIM.destroy_menu = function (event)
+{
+  if (event.target.tagName == "SELECT")
+    document.getElementsByTagName ('body')[0].removeChild (event.target);
+};
+
+MIM.select_menu = function (event)
+{
+  var target = event.target;
+
+  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.im_list)
+    for (var name in MIM.im_list[lang])
+      {
+       var option = document.createElement ('option');
+       var imname = lang + "-" + name;
+       option.appendChild (document.createTextNode (imname));
+       option.value = imname;
+       sel.appendChild (option);
+       if (MIM.im_list[lang][name] == MIM.current)
+         sel.selectedIndex = idx;
+       idx++;
+      }
+  sel.size = idx;
+  document.getElementsByTagName ('body')[0].appendChild (sel);
+};
+
 MIM.test = function ()
 {
   var im = MIM.im_list['t']['latn-post'];
   var ic = new MIM.IC (im);
 
-  document.AIM = im;
-
   ic.Filter (new MIM.Key ('a'));
   ic.Filter (new MIM.Key ("'"));
 
   if (true)
-    document.getElementById ('text').value = ic.preedit;
+    document.getElementById ('text').value = ic.produced + ic.preedit;
   else {
     try {
       document.getElementById ('text').value
@@ -1944,3 +2519,20 @@ 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.im_list['vi']['telex'];
+};
+
+MIM.init_debug = function ()
+{
+  MIM.debug = true;
+  MIM.init ();
+};