using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.IO; using System.Xml; using M17N; using M17N.Core; using M17N.Input; namespace M17N.Input { public class MInputMethod { // Delegaes public delegate bool Callback (MInputContext ic, MPlist args); // Class members public static Callback PreeditStart, PreeditDone, PreeditDraw; public static Callback StatusStart, StatusDone, StatusDraw; public static Callback CandidateStart, CandidateDone, CandidateDraw; public static Callback SetSpot; public static Callback Toggle; public static Callback Reset; public static Callback GetSurroundingText; public static Callback DeleteSurroundingText; internal static MExpression.Domain domain = new MExpression.Domain (null); private static MSymbol Minput_method = "input-method"; private static MSymbol Mdescription = "description"; private static MSymbol Mvariable = "variable"; private static MSymbol Mcommand = "command"; private static MSymbol Mmodule = "module"; private static MSymbol Mmodule_list = "module-list"; private static MSymbol Mtitle = "title"; private static MSymbol Minclude = "include"; private static MSymbol Mmacro = "macro"; private static MSymbol Mmacro_list = "macro-list"; private static MSymbol Mmap = "map"; private static MSymbol Mmap_list = "map-list"; private static MSymbol Mstate = "state"; private static MSymbol Mstate_list = "state-list"; internal static MSymbol Mcandidates = "candidates"; private static MSymbol Minsert = "insert"; private static MSymbol Mdelete = "delete"; private static MSymbol Mmove = "move"; private static MSymbol Mmark = "mark"; private static MSymbol Mmarker = "marker"; private static MSymbol Madd = "add"; private static MSymbol Msub = "sub"; private static MSymbol Mmul = "mul"; private static MSymbol Mdiv = "div"; private static MSymbol Mif = "if"; private static MSymbol Mcond = "cond"; private static MSymbol Mchar_at = "char-at"; private static MSymbol Msurrounding_text_p = "surrounding-text-p"; private static MSymbol Mpushback = "pushback"; private static MSymbol Mkeyseq = "keyseq"; private static Dictionary im_table = new Dictionary (); // Sub classes private class Exception : System.Exception { bool error; public Exception (string msg) : base (msg) { error = true; } public Exception (string fmt, params object[] args) : base (String.Format (fmt, args)) { error = true; } public Exception (string msg, bool error) : base (msg) { this.error = error; } } [FlagsAttribute] private enum LoadStatus { None = 0x00, Header = 0x01, Body = 0x02, Full = 0x03, Error = 0x04, }; [FlagsAttribute] public enum KeyModifier { None = 0x00000000, Shift_L = 0x00400000, Shift_R = 0x00800000, Shift = 0x00C00000, Control_L = 0x01000000, Control_R = 0x02000000, Control = 0x03000000, Alt_L = 0x04000000, Alt_R = 0x08000000, Alt = 0x0C000000, AltGr = 0x10000000, Super = 0x20000000, Hyper = 0x40000000, High = 0x70000000, All = 0x7FC00000, }; public struct Key { internal uint key; private static Dictionary keysyms = new Dictionary (); private static Dictionary keymodifiers = new Dictionary (); private static uint keysym_base = 0x200000; private static uint char_mask = ~((uint) KeyModifier.All); static Key () { keysyms["bs"] = keysyms["backspace"] = 0x08; keysyms["tab"] = 0x09; keysyms["lf"] = keysyms["linefeed"] = 0x10; keysyms["cr"] = keysyms["return"] = keysyms["enter"] = 0x13; keysyms["esc"] = keysyms["escape"] = 0x1B; keysyms["spc"] = keysyms["space"] = 0x20; keysyms["del"] = keysyms["delete"] = 0x7F; keymodifiers["shift-l"] = KeyModifier.Shift_L; keymodifiers["shift-r"] = KeyModifier.Shift_R; keymodifiers["shift"] = KeyModifier.Shift; keymodifiers["control-l"] = KeyModifier.Control_L; keymodifiers["control-r"] = KeyModifier.Control_R; keymodifiers["control"] = KeyModifier.Control; keymodifiers["alt-l"] = KeyModifier.Alt_L; keymodifiers["alt-r"] = KeyModifier.Alt_R; keymodifiers["alt"] = KeyModifier.Alt; keymodifiers["altgr"] = KeyModifier.AltGr; keymodifiers["super"] = KeyModifier.Super; keymodifiers["hyper"] = KeyModifier.Hyper; } private static uint decode_keysym (MSymbol keysym) { uint key; string name = keysym.Name; if (name.Length == 1) return name[0]; name = name.ToLower (); if (! keysyms.TryGetValue (name, out key)) keysyms[name] = key = keysym_base++; return key; } private static uint combine_modifiers (uint c, KeyModifier modifiers) { if (c < 0x7F && c != 0x20) { if ((modifiers & KeyModifier.Shift) != KeyModifier.None && Char.IsLower ((char) c)) { modifiers &= ~KeyModifier.Shift; c = Char.ToUpper ((char) c); } if ((modifiers & KeyModifier.Control) != KeyModifier.None) { modifiers &= ~KeyModifier.Control; c &= 0x1F; } } return c | (uint) modifiers; } public Key (uint c) { key = c; } public Key (uint c, KeyModifier modifiers) { key = combine_modifiers (c, modifiers); } public Key (MSymbol keysym, KeyModifier modifiers) { key = combine_modifiers (decode_keysym (keysym), modifiers); } public Key (MSymbol keysym) { string str = keysym.Name; int len = str.Length; int i; KeyModifier modifiers = KeyModifier.None; for (i = 0; i + 2 < len && str[i + 1] == '-'; i += 2) { if (str[i] == 'S') modifiers |= KeyModifier.Shift; else if (str[i] == 'C') modifiers |= KeyModifier.Control; else if (str[i] == 'A') modifiers |= KeyModifier.Alt; else if (str[i] == 'G') modifiers |= KeyModifier.AltGr; else if (str[i] == 's') modifiers |= KeyModifier.Super; else if (str[i] == 'H') modifiers |= KeyModifier.Hyper; } if (i + 1 == len) key = combine_modifiers (str[i], modifiers); else key = combine_modifiers (decode_keysym (keysym), modifiers); } public Key (MPlist plist) { KeyModifier modifiers = KeyModifier.None; MPlist p; for (p = plist; ! p.IsEmpty; p = p.next) { if (p.IsInteger) { if (! p.next.IsEmpty) throw new Exception ("Invalid Key: " + plist); break; } else if (! p.IsSymbol) throw new Exception ("Invalid Key: " + plist); else { string name = p.Symbol.Name.ToLower (); KeyModifier m; if (! keymodifiers.TryGetValue (name, out m)) break; modifiers |= m; } } if (p.IsEmpty || ! p.next.IsEmpty) throw new Exception ("Invalid Key: " + plist); if (p.IsInteger) key = combine_modifiers ((uint) p.Integer, modifiers); else key = combine_modifiers (decode_keysym (p.Symbol), modifiers); } public bool HasModifier { get { return ((key & (uint) KeyModifier.All) != 0); } } public bool Match (Key k) { if (k.key == key) return true; if ((k.key & char_mask) != (key & char_mask)) return false; KeyModifier m1 = ((KeyModifier) key) & KeyModifier.All; KeyModifier m2 = ((KeyModifier) k.key) & KeyModifier.All; return (((m1 & KeyModifier.Shift) == (m2 & KeyModifier.Shift) || ((m1 & KeyModifier.Shift) == KeyModifier.Shift && (m2 & KeyModifier.Shift) != KeyModifier.None)) && ((m1 & KeyModifier.Control) == (m2 & KeyModifier.Control) || ((m1 & KeyModifier.Control) == KeyModifier.Control && (m2 & KeyModifier.Control) != KeyModifier.None)) && ((m1 & KeyModifier.Alt) == (m2 & KeyModifier.Alt) || ((m1 & KeyModifier.Alt) == KeyModifier.Alt && (m2 & KeyModifier.Alt) != KeyModifier.None)) && ((m1 & KeyModifier.High) == (m2 & KeyModifier.High))); } public override string ToString () { string str = Char.ToString ((char) key); KeyModifier m = ((KeyModifier) key) & KeyModifier.All; if (m != KeyModifier.None) { if ((m & KeyModifier.Shift) != KeyModifier.None) str = "S-" + str; if ((m & KeyModifier.Control) != KeyModifier.None) str = "C-" + str; if ((m & KeyModifier.Alt) != KeyModifier.None) str = "A-" + str; if ((m & KeyModifier.AltGr) != KeyModifier.None) str = "G-" + str; if ((m & KeyModifier.Super) != KeyModifier.None) str = "s-" + str; if ((m & KeyModifier.Hyper) != KeyModifier.None) str = "H-" + str; } return str; } } public class KeySeq : List { public KeySeq () : base () { } public KeySeq (MPlist plist) : base () { foreach (MPlist p in plist) { if (p.IsSymbol) this.Add (new Key (p.Symbol)); else if (p.IsInteger) this.Add (new Key ((char) p.Integer)); else if (p.IsPlist) this.Add (new Key (p.Plist)); else throw new Exception ("Invalid Key Sequence: " + plist); } } public KeySeq (MText mt) : base () { for (int i = 0; i < mt.Length; i++) this.Add (new Key ((uint) mt[i])); } private static uint parse_integer (string str) { if (Char.IsDigit (str[0])) { if (str[0] == '0' && str.Length > 2 && str[1] == 'x') { uint i = 0; for (int idx = 2; idx < str.Length; idx++) { uint c = str[idx]; if (c >= '0' && c <= '9') i = i * 16 + (c - '0'); else if (c >= 'A' && c <= 'F') i = i * 16 + 10 + (c - 'A'); else if (c >= 'a' && c <= 'f') i = i * 16 + 10 + (c - 'a'); else break; } return i; } return UInt32.Parse (str); } else if (str[0] == '?') return str[1]; return 0; } public KeySeq (XmlNode node) : base () { XmlAttributeCollection acol = node.Attributes; XmlNode n; if (acol != null && (n = acol["keys"]) != null) { foreach (char c in n.Value) this.Add (new Key ((uint) c)); } for (node = node.FirstChild; node != null; node = node.NextSibling) { if (node.Name == "key-event") this.Add (new Key ((MSymbol) node.InnerText)); else this.Add (new Key (parse_integer (node.InnerText))); } } public override string ToString () { string str; foreach (Key key in this) if (key.HasModifier) { str = "(keyseq"; foreach (Key k in this) str += " " + k.ToString (); return str + ")"; } str = "\""; foreach (Key key in this) str += key.ToString (); return str + "\""; } } public class Variable { public MSymbol name; public MText description; public Type type; public object value; public object[] candidates; public Variable (MPlist p) { name = p.Symbol; p = p.Next; description = parse_description (p); if (description == null) description = new MText ("No description"); else p = p.next; type = (p.IsMText ? typeof (MText) : p.IsInteger ? typeof (int) : p.IsSymbol ? typeof (MSymbol) : typeof (object)); value = p.val; p = p.next; candidates = new object[p.Count]; for (int i = 0; ! p.IsEmpty; i++, p = p.next) candidates[i] = p.val; } private static Type parse_value (XmlNode node, out object value) { string typename = node.Attributes["type"].Value; Type type; if (typename == "integer") { int i; if (! Int32.TryParse (node.InnerText, out i)) i = 0; value = i; type = typeof (int); } else if (typename == "string") { MText mt = node.InnerText; value = mt; type = typeof (MText); } else if (typename == "symbol") { MSymbol sym = node.InnerText; value = sym; type = typeof (MSymbol); } else { value = null; type = typeof (object); } return type; } public Variable (XmlNode node) { name = node.Attributes["id"].Value; for (node = node.FirstChild; node != null; node = node.NextSibling) if (node.NodeType == XmlNodeType.Element) { if (node.Name == "description") description = parse_description (node); else if (node.Name == "value") type = parse_value (node, out value); else if (node.Name == "valiable-value-candidate") { XmlNodeList n_list = node.ChildNodes; candidates = new object[n_list.Count]; for (int i = 0; i < n_list.Count; i++) { object val; parse_value (n_list[i], out val); candidates[i] = val; } } } } public override string ToString () { return ("(" + name + " \"" + (string) description + "\" " + type + " " + value + " " + candidates + ")"); } } public class Command { public MSymbol name; public MText description; public List keys; public Command (MPlist p) { name = p.Symbol; p = p.Next; description = parse_description (p); if (description == null) description = "No description"; keys = new List (); for (p = p.next; ! p.IsEmpty; p = p.next) { if (p.IsMText) keys.Add (new KeySeq (p.Text)); else if (p.IsPlist) keys.Add (new KeySeq (p.Plist)); } } public Command (XmlNode node) { name = node.Attributes["id"].Value; keys = new List (); for (node = node.FirstChild; node != null; node = node.NextSibling) { if (node.Name == "description") description = parse_description (node); else if (node.Name == "keyseq") keys.Add (new KeySeq (node)); } } public override string ToString () { string str = "(" + name + " \"" + (string) description; foreach (KeySeq keyseq in keys) str += " " + keyseq; return str + ")"; } } internal class Plugin { public string name; public Assembly assembly; public MPlist methods; public override string ToString () { string str = "(" + name; for (MPlist p = methods; ! p.IsEmpty; p = p.next) str += " " + p.key; return str + ")"; } } internal class Map { public MSymbol name; public Dictionary submaps; public MExpression actions; public void Add (KeySeq keys, int index, MExpression actions) { Map sub = null; if (submaps == null) submaps = new Dictionary (); else submaps.TryGetValue (keys[index], out sub); if (sub == null) { Key key = keys[index]; submaps[key] = sub = new Map (); } if (index + 1 < keys.Count) sub.Add (keys, index + 1, actions); else this.actions = actions; } public MExpression Lookup (KeySeq keys, int index) { Map sub; if (index + 1 == keys.Count) return actions; if (submaps.TryGetValue (keys[index], out sub)) return sub.Lookup (keys, index + 1); return null; } private void describe (MText mt, KeySeq keyseq) { if (keyseq.Count > 0) { mt.Cat (" (").Cat (keyseq.ToString ()); if (actions != null) mt.Cat (' ').Cat (actions.ToString ()); mt.Cat (')'); } if (submaps != null) foreach (KeyValuePair kv in submaps) { keyseq.Add (kv.Key); kv.Value.describe (mt, keyseq); keyseq.RemoveAt (keyseq.Count - 1); } } public override string ToString () { MText mt = "(" + name.Name; KeySeq keyseq = new KeySeq (); describe (mt, keyseq); mt.Cat (')'); return (string) mt; } } internal class State { public MSymbol name; public MText title; public MPlist branches = new MPlist (); public State (MSymbol name) { this.name = name; } public override string ToString () { MText mt = "(" + name.Name; if (title != null) mt.Cat (" \"" + title + "\""); for (MPlist p = branches; ! p.IsEmpty; p = p.next) mt.Cat (" (" + p.Key + " " + (MExpression) p.Val + ")"); return (string) mt + ")"; } } // Instance members internal MExpression.Domain local_domain; private LoadStatus load_status = LoadStatus.None; private MDatabase.Tag tag; private MDatabase mdb; private MText description; internal MText title; internal Command[] commands; internal Variable[] variables; internal Dictionary plugins; internal MPlist bindings; internal Dictionary maps; internal MPlist states; static MInputMethod () { domain.Defun ("insert", insert, 1, 1); domain.Defun ("candidates", insert_candidates, 1, -1); domain.Defun ("delete", delete, 1, 1); domain.Defun ("select", select, 1, 1); domain.Defun ("show", show, 0, 0); domain.Defun ("hide", hide, 0, 0); domain.Defun ("move", move, 1, 1); domain.Defun ("mark", mark, 1, 1, true); domain.Defun ("pushback", pushback, 1, 1); domain.Defun ("pop", pop, 0, 0); domain.Defun ("undo", undo, 0, 1); domain.Defun ("commit", commit, 0, 0); domain.Defun ("unhandle", unhandle, 0, 0); domain.Defun ("shift", shift, 1, 1, true); domain.Defun ("call", call, 2, -1, true); // Pseudo functions to make arguments of special objects. domain.Defun ("marker", marker, 1, 1, true); domain.Defun ("char-at", char_at, 1, 1, true); domain.Defun ("keyseq", keyseq, 1, -1, true); MDatabase.Tag tag = new MDatabase.Tag (Minput_method, "*", "*", "*"); List list = MDatabase.List (tag); M17n.DebugPrint ("Found {0} input methods\n", list.Count); foreach (MDatabase mdb in list) im_table[mdb.tag] = new MInputMethod (mdb.tag); } // Constructor private MInputMethod (MDatabase.Tag tag) { this.tag = tag; } // Instance Properties public MSymbol Language { get { return tag[1]; } } public MSymbol Name { get { return tag[2]; } } public MSymbol SubName { get { return tag[3]; } } public bool Info (out MText description, out MText title, out Variable[] variables, out Command[] commands) { if ((load_status & LoadStatus.Header) != LoadStatus.Header && ! load_header ()) { description = null; title = null; variables = null; commands = null; return false; } description = this.description; title = this.title; variables = this.variables; commands = this.commands; return true; } public static MInputMethod Find (MSymbol language, MSymbol name) { return Find (language, name, MSymbol.nil); } public static MInputMethod Find (MSymbol language, MSymbol name, MSymbol subname) { MDatabase.Tag tag = new MDatabase.Tag (Minput_method, language, name, subname); MInputMethod im; return (im_table.TryGetValue (tag, out im) ? im : null); } public bool Open () { return ((load_status == LoadStatus.Full) || load_body ()); } public static MInputMethod[] List () { MInputMethod[] array = new MInputMethod[im_table.Count]; int i = 0; foreach (KeyValuePair kv in im_table) array[i++] = kv.Value; return array; } private bool load_header () { mdb = MDatabase.Find (tag); if (mdb == null) return false; try { MSymbol format = mdb.Format; if (format == MSymbol.plist) load ((MPlist) mdb.Load (Mmap), false); else { XmlDocument doc = (XmlDocument) mdb.Load (Mmap_list); load (doc.DocumentElement, false); } } catch (Exception e) { Console.WriteLine ("{0}\n", e); load_status = LoadStatus.Error; return false; } load_status |= LoadStatus.Header; return true; } private bool load_body () { local_domain = new MExpression.Domain (domain, null); mdb = MDatabase.Find (tag); if (mdb == null) return false; try { object obj = mdb.Load (); if (obj is MPlist) load ((MPlist) obj, true); else load ((XmlDocument) obj, true); } catch (Exception e) { Console.WriteLine (e); load_status = LoadStatus.Error; return false; } load_status = LoadStatus.Full; return true; } private void load (MPlist plist, bool full) { maps = new Dictionary (); states = new MPlist (); for (; ! plist.IsEmpty; plist = plist.next) if (plist.IsPlist) { MPlist pl = plist.Plist; if (pl.IsSymbol) { MSymbol sym = pl.Symbol; pl = pl.next; if (sym == Mdescription) { description = parse_description (pl); if (description == null) description = new MText ("No description"); } else if (sym == Mtitle) { if (pl.IsMText) title = pl.Text; } else if (sym == Mvariable) parse_variables (pl); else if (sym == Mcommand) parse_commands (pl); else if (full) { if (sym == Mmodule) parse_plugins (pl); else if (sym == Minclude) parse_include (pl); else if (sym == Mmacro) parse_macros (pl); else if (sym == Mmap) parse_maps (pl); else if (sym == Mstate) parse_states (pl); } } } if (description == null) description = (MText) "No description"; if (title == null) title = new MText (tag[2].Name); if (variables == null) variables = new Variable[0]; if (commands == null) commands = new Command[0]; if (! full) return; if (states.IsEmpty) { State state = new State ((MSymbol) "init"); plist = new MPlist (); foreach (KeyValuePairkv in maps) state.branches.Add (kv.Key, new MExpression (plist, local_domain)); states.Add (state.name, state); } } private void load (XmlNode node, bool full) { bool skip_header = load_status == LoadStatus.Header; maps = new Dictionary (); states = new MPlist (); if (node.NodeType == XmlNodeType.Document) node = node.FirstChild; while (node.NodeType != XmlNodeType.Element) node = node.NextSibling; for (node = node.FirstChild; node != null; node = node.NextSibling) { if (node.NodeType != XmlNodeType.Element) continue; if (! skip_header) { if (node.Name == "description") description = parse_description (node); else if (node.Name == "title") title = parse_title (node); else if (node.Name == "variable-list") parse_variables (node); else if (node.Name == "command-list") parse_commands (node); } else if (full) { if (node.Name == "module-list") parse_plugins (node); else if (node.Name == "macro-list") parse_macros (node); else if (node.Name == "map-list") parse_maps (node); else if (node.Name == "state-list") parse_states (node); } } if (description == null) description = (MText) "No description"; if (title == null) title = new MText (tag[2].Name); if (variables == null) variables = new Variable[0]; if (commands == null) commands = new Command[0]; if (! full) return; if (states.IsEmpty) { State state = new State ((MSymbol) "init"); MPlist plist = new MPlist (); foreach (KeyValuePairkv in maps) state.branches.Add (kv.Key, new MExpression (plist, local_domain)); states.Add (state.name, state); } } private static void transform (MPlist plist) { for (; ! plist.IsEmpty; plist = plist.next) { if (plist.IsMText) { MPlist p = new MPlist (); p.Add (MSymbol.symbol, Minsert); p.Add (MSymbol.mtext, plist.Text); plist.Set (MSymbol.plist, p); } else if (plist.IsInteger) { MPlist p = new MPlist (); p.Add (MSymbol.symbol, Minsert); p.Add (MSymbol.integer, plist.Integer); plist.Set (MSymbol.plist, p); } else if (plist.IsPlist) { MPlist pl = plist.Plist; if (pl.IsSymbol) { if (pl.Symbol == Madd) pl.Set (MSymbol.symbol, (MSymbol) "+="); else if (pl.Symbol == Msub) pl.Set (MSymbol.symbol, (MSymbol) "-="); else if (pl.Symbol == Mmul) pl.Set (MSymbol.symbol, (MSymbol) "*="); else if (pl.Symbol == Mdiv) pl.Set (MSymbol.symbol, (MSymbol) "/="); else if (pl.Symbol == Minsert) { // (insert (CANDIDATES ...)) // => (candidates CANDIDATES ...) if (pl.next.IsPlist) { pl.Set (MSymbol.symbol, Mcandidates); pl = pl.next; MPlist p = pl.Plist; pl.Set (p.key, p.val); for (p = p.next; ! p.IsEmpty; p = p.next); pl.Add (p.key, p.val); } } else if (pl.Symbol == Mif) { pl = pl.next; if (! pl.IsEmpty) transform (pl.next); } else if (pl.Symbol == Mcond) { for (pl = pl.next; ! pl.IsEmpty; pl = pl.next) if (pl.IsPlist) { MPlist p = pl.Plist; if (p.IsPlist) transform (p); else transform (p.next); } } else if (pl.Symbol == Mdelete || pl.Symbol == Mmove || pl.Symbol == Mmark) { pl = pl.next; if (pl.IsSymbol) { MSymbol sym = pl.Symbol; MPlist p = new MPlist (); p.Add (MSymbol.symbol, Mmarker); p.Add (MSymbol.symbol, sym); pl.Set (MSymbol.plist, p); } } else if (pl.Symbol == Mpushback) { pl = pl.next; if (pl.IsPlist) pl.Plist.Push (MSymbol.symbol, Mkeyseq); } } else if (pl.IsMText) { // (CANDIDATES ...) => (candidates CANDIDATES ...) pl.Push (MSymbol.symbol, Mcandidates); } } else if (plist.IsSymbol) { MSymbol sym = plist.Symbol; if (sym.Name.Length >= 3 && sym.Name[0] == '@' && (sym.Name[1] == '-' || sym.Name[1] == '+')) { int pos = int.Parse (sym.Name.Substring (1)); MPlist p = new MPlist (); if (pos == 0) { p.Add (MSymbol.symbol, Msurrounding_text_p); } else { if (sym.Name[1] == '+') pos--; p.Add (MSymbol.symbol, Mchar_at); p.Add (MSymbol.integer, pos); } plist.Set (MSymbol.plist, p); } } } } private static MText parse_description (MPlist plist) { if (plist.IsMText) return plist.Text; if (plist.IsPlist) { plist = plist.Plist; if (plist.IsSymbol && plist.Symbol == (MSymbol) "_" && plist.next.IsMText) return plist.next.Text; } return null; } private static MText parse_description (XmlNode node) { if (node.HasChildNodes) node = node.FirstChild; return node.InnerText; } private static MText parse_title (XmlNode node) { return node.InnerText; } private void parse_variables (MPlist plist) { variables = new Variable[plist.Count]; for (int i = 0; ! plist.IsEmpty; plist = plist.next) if (plist.IsPlist && plist.Plist.IsSymbol) variables[i++] = new Variable (plist.Plist); } private void parse_variables (XmlNode node) { XmlNodeList node_list = node.ChildNodes; variables = new Variable[node_list.Count]; for (int i = 0; i < node_list.Count; i++) if (node_list[i].NodeType == XmlNodeType.Element) variables[i] = new Variable (node_list[i]); } private void parse_commands (MPlist plist) { commands = new Command[plist.Count]; for (int i = 0; ! plist.IsEmpty; plist = plist.next) if (plist.IsPlist && plist.Plist.IsSymbol) commands[i++] = new Command (plist.Plist); } private void parse_commands (XmlNode node) { XmlNodeList node_list = node.ChildNodes; commands = new Command[node_list.Count]; for (int i = 0; i < node_list.Count; i++) { if (node_list[i].NodeType == XmlNodeType.Element) commands[i] = new Command (node_list[i]); } } private void parse_plugins (MPlist plist) { plugins = new Dictionary (); for (; ! plist.IsEmpty; plist = plist.Next) { MPlist p = plist.Plist; MSymbol sym = p.Symbol; Plugin plugin = new Plugin (); plugin.name = sym.Name; plugin.methods = new MPlist (); for (p = p.next; ! p.IsEmpty; p = p.next) plugin.methods.Add (p.Symbol, null); plugins.Add (sym, plugin); } } private void parse_plugins (XmlNode node) { plugins = new Dictionary (); foreach (XmlNode n in node.ChildNodes) { Plugin plugin = new Plugin (); plugin.name = n.Attributes["id"].Value; plugin.methods = new MPlist (); foreach (XmlNode nn in n.ChildNodes) plugin.methods.Add ((MSymbol) nn.Attributes["id"].Value, null); plugins.Add (plugin.name, plugin); } } private void parse_macros (XmlNode node) { for (XmlNode nn = node.FirstChild; nn != null; nn = nn.NextSibling) if (nn.NodeType == XmlNodeType.Element && nn.Name == "xi:include") { XmlNode n = nn.FirstChild.FirstChild; MSymbol language = n.InnerText; n = n.NextSibling; MSymbol name = n.InnerText; n = n.NextSibling; MSymbol subname = (n != null ? n.InnerText : MSymbol.nil); n = n.ParentNode.NextSibling; MSymbol section = n.InnerText; n = n.NextSibling; MSymbol id = (n != null ? n.InnerText : MSymbol.nil); MInputMethod im = MInputMethod.Find (language, name, subname); if (im == null || ! im.Open ()) continue; if (id == MSymbol.nil) im.local_domain.CopyFunc (local_domain); else im.local_domain.CopyFunc (local_domain, id); } for (XmlNode nn = node.FirstChild; nn != null; nn = nn.NextSibling) if (nn.NodeType == XmlNodeType.Element && nn.Name != "xi:include") local_domain.Defun ((MSymbol) node.GetAttribute ("id")); for (XmlNode nn = node.FirstChild; nn != null; nn = nn.NextSibling) if (nn.NodeType == XmlNodeType.Element && nn.Name != "xi:include") local_domain.Defun ((MSymbol) node.GetAttribute ("id"), null, nn.FirstChild); } private void parse_maps (XmlNode node) { } private void parse_states (XmlNode node) { } private void parse_include (MPlist plist) { if (! plist.IsPlist) return; MPlist p = plist.Plist; MSymbol language, name, subname; language = p.Symbol; p = p.next; if (! p.IsSymbol) name = subname = MSymbol.nil; else { name = p.Symbol; p = p.next; if (! p.IsSymbol) subname = MSymbol.nil; else subname = p.Symbol; } MInputMethod im = MInputMethod.Find (language, name, subname); if (im == null) return; if (! im.Open ()) return; plist = plist.next; if (! plist.IsSymbol) return; MSymbol target_type = plist.Symbol; plist = plist.next; MSymbol target_name = MSymbol.nil; if (plist.IsSymbol) target_name = plist.Symbol; if (target_type == Mmacro) { if (target_name == MSymbol.nil) im.local_domain.CopyFunc (local_domain); else im.local_domain.CopyFunc (local_domain, target_name); } else if (target_type == Mmap) { if (target_name == MSymbol.nil) { foreach (KeyValuePair kv in im.maps) maps[kv.Key] = kv.Value; } else { Map map; if (im.maps.TryGetValue (target_name, out map)) maps[target_name] = map; } } else if (target_type == Mstate) { if (target_name == MSymbol.nil) { for (p = im.states; ! p.IsEmpty; p = p.next) states.Add (p.key, p.val); } else { object state = im.states.Get (target_name); if (state != null) states.Add (target_name, state); } } } private void parse_macros (MPlist plist) { for (MPlist pl = plist; ! pl.IsEmpty; pl = pl.next) if (pl.IsPlist) { MPlist p = pl.Plist; if (! p.IsSymbol) continue; local_domain.Defun (p.Symbol, null, null); } for (MPlist pl = plist; ! pl.IsEmpty; pl = pl.next) if (pl.IsPlist) { MPlist p = pl.Plist; if (! p.IsSymbol) continue; transform (p.next); local_domain.Defun (p.Symbol, null, p.next); } } private void parse_maps (MPlist plist) { for (; ! plist.IsEmpty; plist = plist.next) if (plist.IsPlist) { MPlist pl = plist.Plist; if (! pl.IsSymbol) continue; Map map = new Map (); map.name = pl.Symbol; maps[map.name] = map; for (pl = pl.next; ! pl.IsEmpty; pl = pl.next) { if (! pl.IsPlist) continue; MPlist p = pl.Plist; KeySeq keys; if (p.IsMText) keys = new KeySeq (p.Text); else if (p.IsPlist) keys = new KeySeq (p.Plist); else continue; p = p.next; if (p.IsEmpty) continue; transform (p); MExpression expr = new MExpression (p, local_domain); map.Add (keys, 0, expr); } } } private void parse_states (MPlist plist) { for (; ! plist.IsEmpty; plist = plist.next) if (plist.IsPlist) { MPlist pl = plist.Plist; MText title = null; if (pl.IsMText) { title = pl.Text; pl = pl.next; } if (! pl.IsSymbol) continue; State state = new State (pl.Symbol); state.title = title; if (states == null) states = new MPlist (); states.Add (state.name, state); for (pl = pl.next; ! pl.IsEmpty; pl = pl.next) { if (! pl.IsPlist) continue; MPlist p = pl.Plist; if (! p.IsSymbol) continue; MSymbol map_name = p.Symbol; p = p.next; transform (p); state.branches.Add (map_name, new MExpression (p, local_domain)); } } } private static object insert (MExpression[] args, MExpression.Domain domain) { ((MInputContext) domain.context).insert (args[0].Val); return true; } private static object insert_candidates (MExpression[] args, MExpression.Domain domain) { ((MInputContext) domain.context).insert_candidates ((MPlist) args[0].Val); return true; } private static object marker (MExpression[] args, MExpression.Domain domain) { MSymbol sym = (MSymbol) args[0].Args[0].Val; return ((MInputContext) domain.context).marker (sym); } private static object char_at (MExpression[] args, MExpression.Domain domain) { return ((MInputContext) domain.context).char_at ((int) args[0].Val); } private static object delete (MExpression[] args, MExpression.Domain domain) { ((MInputContext) domain.context).delete ((int) args[0].Val); return true; } private static object select (MExpression[] args, MExpression.Domain domain) { MInputContext ic = (MInputContext) domain.context; object val = args[0].Val; if (val is int) ic.select ((int) val); else ic.select ((MSymbol) val); return true; } private static object show (MExpression[] args, MExpression.Domain domain) { ((MInputContext) domain.context).show (); return true; } private static object hide (MExpression[] args, MExpression.Domain domain) { ((MInputContext) domain.context).hide (); return true; } private static object move (MExpression[] args, MExpression.Domain domain) { ((MInputContext) domain.context).move ((int) args[0].Val); return true; } private static object mark (MExpression[] args, MExpression.Domain domain) { MSymbol sym = (MSymbol) args[0].Val; ((MInputContext) domain.context).mark (sym); return true; } private static object keyseq (MExpression[] args, MExpression.Domain domain) { MPlist p = new MPlist (); for (int i = 0; i < args.Length; i++) p.Add (MSymbol.symbol, (MSymbol) args[i].Val); return new KeySeq (p); } private static object pushback (MExpression[] args, MExpression.Domain domain) { MInputContext ic = (MInputContext) domain.context; object val = args[0].Val; if (val is int) ic.pushback ((int) val); else if (val is MText) ic.pushback (new KeySeq ((MText) val)); else if (val is KeySeq) ic.pushback ((KeySeq) val); else throw new Exception ("Invalid keyseq: " + val); return true; } private static object pop (MExpression[] args, MExpression.Domain domain) { ((MInputContext) domain.context).pop (); return true; } private static object undo (MExpression[] args, MExpression.Domain domain) { int n = args.Length == 0 ? -2 : (int) args[0].Val; ((MInputContext) domain.context).undo (n); return true; } private static object commit (MExpression[] args, MExpression.Domain domain) { ((MInputContext) domain.context).commit (); return true; } private static object unhandle (MExpression[] args, MExpression.Domain domain) { ((MInputContext) domain.context).commit (); return false; } private static object shift (MExpression[] args, MExpression.Domain domain) { MSymbol sym = (MSymbol) args[0].Args[0].Val; ((MInputContext) domain.context).shift (sym); return true; } private static object call (MExpression[] args, MExpression.Domain domain) { MSymbol module = (MSymbol) args[0].Args[0].Val; MSymbol method = (MSymbol) args[1].Args[0].Val; MPlist arglist = new MPlist (); for (int i = 2; i < args.Length; i++) { object val = args[i].Eval (domain); if (val is int) arglist.Add (MSymbol.integer, val); else if (val is MSymbol) arglist.Add (MSymbol.symbol, val); else if (val is MText) arglist.Add (MSymbol.mtext, val); else if (val is MPlist) arglist.Add (MSymbol.plist, val); else throw new Exception ("Invalid argument to {0}/{1}: {2}", module, method, val); } return ((MInputContext) domain.context).call (module, method, arglist); } public override string ToString () { string str = (String.Format ("({0} (title \"{1}\")", tag, title)); if (commands != null) { str += " (commands"; foreach (Command cmd in commands) str += " " + cmd; str += ")"; } if (variables != null) { str += " (variables"; foreach (Variable var in variables) str += " " + var; str += ")"; } if (plugins != null) { str += " (modules"; foreach (KeyValuePair kv in plugins) str += " " + kv.Value; str += ")"; } str += " (maps"; foreach (KeyValuePair kv in maps) str += " " + kv.Value; str += ") (states"; foreach (MPlist p in states) str += " " + p.val; return str + "))"; } } public class MInputContext { internal static MSymbol Mcandidates_group_size = "candidates-group-size"; private static MSymbol Mat_less_than = "@<"; private static MSymbol Mat_greater_than = "@>"; private static MSymbol Mat_minus = "@-"; private static MSymbol Mat_plus = "@+"; private static MSymbol Mat_open_square_bracket = "@["; private static MSymbol Mat_close_square_bracket = "@]"; public MInputMethod im; private MText produced; private bool active; private MText status; private bool status_changed; private MText preedit; private bool preedit_changed; private int cursor_pos; private bool cursor_pos_changed; private Candidates candidates; private MPlist candidate_group; private int candidate_index; private int candidate_from, candidate_to; private bool candidate_show; private bool candidate_changed; private Stack states; internal MInputMethod.KeySeq keys; private int key_head; private int state_key_head; private MPlist state_boundary; private int commit_key_head; private MText state_preedit; private int state_pos; internal MPlist markers = new MPlist (); private MPlist vars; private MPlist vars_saved; internal MText preceding_text = new MText (); internal MText following_text = new MText (); private bool key_unhandled; internal MExpression.Domain domain; public MInputContext (MInputMethod im) { this.im = im; domain = new MExpression.Domain (im.local_domain, this); states = new Stack (); states.Push ((MInputMethod.State) im.states.val); keys = new MInputMethod.KeySeq (); } private void adjust_markers (int from, int to, object inserted) { int ins = (inserted == null ? 0 : inserted is int ? 1 : ((MText) inserted).Length); int diff = ins - (to - from); for (MPlist plist = markers; ! plist.IsEmpty; plist = plist.next) { int pos = plist.Integer; if (pos > from) { if (pos >= to) plist.val = pos + diff; else plist.val = from; } } if (cursor_pos >= to) cursor_pos += diff; else if (cursor_pos > from) cursor_pos = from; } private void preedit_replace (int from, int to, int c) { preedit.Del (from, to); preedit.Ins (from, c); adjust_markers (from, to, c); } private void preedit_replace (int from, int to, MText mt) { preedit[from, to] = mt; adjust_markers (from, to, mt); } internal void insert (object arg) { if (arg is int) preedit_replace (cursor_pos, cursor_pos, (int) arg); else preedit_replace (cursor_pos, cursor_pos, (MText) arg); preedit_changed = true; cursor_pos_changed = true; } private class Candidates { private class Block { public int Index; public object Data; public Block (int index, MPlist plist) { Index = index; if (plist.IsMText) Data = plist.Text; else Data = plist.Plist; } public int Count { get { return (Data is MText ? ((MText) Data).Length : ((MPlist) Data).Count); } } public object this[int i] { get { if (Data is MText) return ((MText) Data)[i]; return ((MPlist) Data)[i]; } } } private Block[] blocks; private int row = 0; private int index = 0; public object[] group; private bool IsFixed { get { return group != null; } } private int Total { get { Block last = blocks[blocks.Length - 1]; return last.Index + last.Count; } } public int Column { get { return (IsFixed ? index % group.Length : index - blocks[row].Index); } } public object Group { get { return (IsFixed ? group : blocks[row].Data); } } public int GroupLength { get { if (IsFixed) { int nitems = group.Length; int start = index - (index % nitems); int total = Total; return (start + nitems <= total ? nitems : total - start); } return blocks[row].Count; } } public object Current { get { return (IsFixed ? group[index % group.Length] : blocks[row][index - blocks[row].Index]); } } public Candidates (MPlist list, int column) { int nblocks = list.Count; blocks = new Block[nblocks]; for (int i = 0, start = 0; i < nblocks; i++, list = list.next) start += (blocks[i] = new Block (index, list)).Count; if (column > 0) group = new object[column]; } public static void Detach (MInputContext ic) { ic.preedit.PopProp (0, ic.preedit.Length, MInputMethod.Mcandidates); ic.candidates = null; ic.preedit_changed = true; ic.cursor_pos_changed = true; ic.candidate_changed = true; } // Fill the array "group" by candidates stating from INDEX. // INDEX must be a multiple of "column". Set NTIMES to the // number of valid candidates in "group". Update "block" if // necessary. Return "group". private int fill_group (int start) { int nitems = group.Length; int r = row; Block b = blocks[r]; if (start < b.Index) while (start < b.Index) b = blocks[--r]; else while (start >= b.Index + b.Count) b = blocks[++r]; row = r; int count = b.Count; start -= b.Index; for (int i = 0; i < nitems; i++, start++) { if (start >= count) { r++; if (r == blocks.Length) return i; b = blocks[r]; count = b.Count; start = 0; } group[i] = b[start]; } return nitems; } // Update "row" to what contains the first candidate of // the previous candidate-group, update "current_index", and // update "group" if necessary. Return the previous // candidate-group. Set NITEMS to the number of valid // candidates contained in that group. public int PrevGroup () { int nitems; int col = Column; if (IsFixed) { nitems = group.Length; if ((index -= col + nitems) < 0) index = (Total / nitems) * nitems; nitems = fill_group (index); } else { row = row > 0 ? row-- : blocks.Length - 1; nitems = blocks[row].Count; index = blocks[row].Index; } index += col < nitems ? col : nitems - 1; return nitems; } public int NextGroup () { int nitems; int col = Column; if (IsFixed) { nitems = group.Length; if ((index += nitems - col) >= Total) index = 0; nitems = fill_group (index); } else { row = row < blocks.Length - 1 ? row + 1 : 0; nitems = blocks[row].Count; index = blocks[row].Count; } index += col < nitems ? col : nitems - 1; return nitems; } public void Prev () { int col = Column; if (col == 0) { int nitems = PrevGroup (); index += col < nitems - 1 ? col : nitems - 1; } else index--; } public void Next () { int col = Column; int nitems = GroupLength; if (col == nitems - 1) { nitems = NextGroup (); index -= Column; } else index++; } public void First () { index -= Column; } public void Last () { index += GroupLength - (Column + 1); } public void Select (int col) { int maxcol = GroupLength - 1; if (col > maxcol) col = maxcol; index = index - Column + col; } } private void update_candidate () { object candidate = candidates.Current; if (candidate is MText) { preedit_replace (candidate_from, candidate_to, (MText) candidate); candidate_to = candidate_from + ((MText) candidate).Length; } else { preedit_replace (candidate_from, candidate_to, (int) candidate); candidate_to = candidate_from + 1; } preedit.PushProp (candidate_from, candidate_to, MInputMethod.Mcandidates, this); cursor_pos = candidate_from; preedit_changed = true; cursor_pos_changed = true; candidate_changed = true; } internal void insert_candidates (MPlist list) { int column = 0; if (domain.IsBound (Mcandidates_group_size)) { object val = domain.GetValue (Mcandidates_group_size); if (val is int) column = (int) val; } candidates = new Candidates (list, column); candidate_from = candidate_to = cursor_pos; update_candidate (); } internal void select (int n) { if (candidates != null) { candidates.Select (n); update_candidate (); } } internal void select (MSymbol sym) { if (candidates != null) { if (sym == Mat_less_than) candidates.First (); else if (sym == Mat_greater_than) candidates.Last (); else if (sym == Mat_minus) candidates.Prev (); else if (sym == Mat_plus) candidates.Next (); else if (sym == Mat_open_square_bracket) candidates.PrevGroup (); else if (sym == Mat_close_square_bracket) candidates.NextGroup (); } } internal int marker (MSymbol sym) { int pos = cursor_pos; if (sym.Name.Length == 2 && sym.Name[0] == '@') { switch (sym.Name[0]) { case '<': pos = 0; break; case '>': pos = preedit.Length; break; case '-': pos = cursor_pos - 1; break; case '+': pos = cursor_pos + 1; break; case '[': if (pos > 0) { int to; preedit.FindProp (MInputMethod.Mcandidates, pos - 1, out pos, out to); } else pos = 0; break; case ']': if (cursor_pos < preedit.Length - 1) { int from; preedit.FindProp (MInputMethod.Mcandidates, pos, out from, out pos); } else pos = preedit.Length; break; default: if (sym.Name[0] >= '0' && sym.Name[0] <= '9') pos = sym.Name[0]; break; } } else if (sym.Name.Length >= 3 && sym.Name[0] == '@') { pos = int.Parse (sym.Name.Substring (2)); } else { object val = markers.Get (sym); if (val is int) pos = (int) val; } return pos; } internal int char_at (int pos) { int c; pos += cursor_pos; if (pos < 0) { if (preceding_text.Length < -pos) { MPlist plist = new MPlist (); plist.Push (MSymbol.integer, pos); if (MInputMethod.GetSurroundingText != null && MInputMethod.GetSurroundingText (this, plist) && plist.IsMText && preceding_text.Length < plist.Text.Length) preceding_text = plist.Text; } c = (-pos < preceding_text.Length ? preceding_text[preceding_text.Length + pos] : -1); } else if (pos >= 0 && pos < preedit.Length) c = preedit[pos]; else { pos -= preedit.Length; if (pos >= following_text.Length) { MPlist plist = new MPlist (); plist.Push (MSymbol.integer, pos + 1); if (MInputMethod.GetSurroundingText != null && MInputMethod.GetSurroundingText (this, plist) && plist.IsMText && following_text.Length < plist.Text.Length) following_text = plist.Text; } c = (pos < following_text.Length ? following_text[pos] : -1); } return c; } internal void delete (int pos) { if (pos < cursor_pos) preedit_replace (pos, cursor_pos, null); else preedit_replace (cursor_pos, pos, null); preedit_changed = true; cursor_pos_changed = true; } internal void show () { candidate_show = true; candidate_changed = true; } internal void hide () { candidate_show = false; candidate_changed = true; } internal void move (int pos) { if (pos < 0) pos = 0; else if (pos > preedit.Length) pos = preedit.Length; if (pos != cursor_pos) { cursor_pos = pos; preedit_changed = true; } } internal void mark (MSymbol sym) { MPlist slot = markers.Find (sym); if (slot == null) markers.Push (sym, cursor_pos); else slot.val = cursor_pos; } internal void pushback (int n) { if (n > 0) { key_head -= n; if (key_head < 0) key_head = 0; } else if (n == 0) key_head = 0; else { key_head = - n; if (key_head > keys.Count) key_head = keys.Count; } } internal void pushback (MInputMethod.KeySeq keyseq) { if (key_head > 0) key_head--; if (key_head < keys.Count) keys.RemoveRange (key_head, keys.Count - key_head); for (int i = 0; i < keyseq.Count; i++) keys.Add (keyseq[i]); } internal void pop () { if (key_head < keys.Count) keys.RemoveRange (key_head, 1); } internal void undo (int n) { if (n < 0) keys.RemoveRange (keys.Count + n, - n); else keys.RemoveRange (n, keys.Count - n); reset (); } internal void commit () { produced.Cat (preedit); preedit.Del (); preedit_changed = true; } internal void shift (MSymbol sym) { MInputMethod.State state; if (sym == MSymbol.t) { if (states.Count > 1) state = states.Pop (); else state = states.Peek (); } else { state = (MInputMethod.State) im.states.Get (sym); if (state == null) throw new Exception ("Unknown state: " + state.name); } if (state == null) state = states.Pop (); if (state == (MInputMethod.State) im.states.val) { commit (); reset (); } else { state_key_head = key_head; state_pos = cursor_pos; state_preedit = preedit.Dup (); if (state != states.Peek ()) { states.Push (state); state_boundary = domain.SetBoundary (); status = state.title; if (status == null) status = im.title; status_changed = true; MExpression on_entry = (MExpression) state.branches.Get (MSymbol.t); if (on_entry != null) on_entry.Eval (domain); } } } internal bool call (MSymbol module, MSymbol method, MPlist arglist) { MInputMethod.Plugin plugin; if (! im.plugins.TryGetValue (module, out plugin)) return false; if (plugin.assembly == null) { Assembly assembly; try { assembly = Assembly.LoadFrom (module.Name + ".dll"); } catch { return false; } Type t = assembly.GetType ("Plugin"); for (MPlist p = plugin.methods; ! p.IsEmpty; p = p.next) p.Set (p.key, t.GetMethod (p.key.Name)); } MethodInfo method_info = (MethodInfo) plugin.methods.Get (method); if (method_info == null) return false; object[] method_arg = new object[1]; method_arg[0] = arglist; bool result = (bool) method_info.Invoke (null, method_arg); if (! result) return false; if (! arglist.IsEmpty) (new MExpression (arglist, domain)).Eval (domain); return true; } internal void reset () { preedit.Del (); state_preedit.Del (); produced.Del (); markers.Clear (); cursor_pos = 0; key_head = commit_key_head = 0; states.Clear (); states.Push ((MInputMethod.State) im.states.Val); state_key_head = 0; state_pos = 0; } internal object GetCandidates (out int column) { column = 0; if (cursor_pos == 0) return null; Candidates candidates = (Candidates) preedit.GetProp (cursor_pos - 1, MInputMethod.Mcandidates); if (candidates == null) return null; column = candidates.Column; return candidates.Current; } internal void HandleKey () { MInputMethod.State state = states.Peek (); } } }