New Suggestion module
diff --git a/public/js/hint.js b/public/js/hint.js
index 80c7d25..eed45f3 100644
--- a/public/js/hint.js
+++ b/public/js/hint.js
@@ -1,248 +1,813 @@
-/*
-TODO:
-  - Limit the size to a certain number of elements
-  - addEventListener("click", ... , false);
-  - addEventListener("paste", ... , false);
-  - Make this a general purpose hint-System with left-context-suport
-  - Die Funktion, wann was angezeigt werden soll, sollte extern
-    definiert sein (der Kontext / changed)
-  - Die Werteliste sollte weitere Attribute enthalten, wie title und class
-*/
+"use strict";
 
-var Hint = function (param) {
-  var foundryRegex = new RegExp("(?:^|[^a-zA-Z0-9])([-a-zA-Z0-9]+?)\/(?:([^=]+?)=)?$");
+// Don't let events bubble up
+Event.prototype.halt = function () {
+  this.stopPropagation();
+  this.preventDefault();
+};
 
-  var search =   document.getElementById(param["ref"]);
-  var qlField =  document.getElementById(param["qlRef"]);
-  var mirror =   document.createElement("div");
-  var hint =     document.createElement("ul");
-  var hintSize = param["hintSize"] ? param["hintSize"] : 10;
-  var hints =    param["hints"];
-  var that = this;
-  var ql;
+// http://www.nlpado.de/~sebastian/software/ner_german.shtml
+// http://www.cnts.ua.ac.be/conll2003/ner/
+var namedEntities = [
+  ["I-LOC",  "I-LOC",  "Location"],
+  ["I-MISC", "I-MISC", "Miscellaneous"],
+  ["I-ORG",  "I-ORG",  "Organization"],
+  ["I-PER",  "I-PER",  "Person"]
+];
 
-  // Build the mirror element
-  // <div id="searchMirror"><span></span><ul></ul></div>
-  mirror.setAttribute("id", "searchMirror");
-  mirror.appendChild(document.createElement("span"));
-  mirror.appendChild(hint);
-  document.getElementsByTagName("body")[0].appendChild(mirror);
-  // Default active state
-  this.active = -2;
+// http://www.ids-mannheim.de/cosmas2/projekt/referenz/stts/morph.html
+// http://nachhalt.sfb632.uni-potsdam.de/owl-docu/stts.html
+var sttsArray = [
+  // "$.", "$(", "$,"
+  ["ADJA","ADJA", "Attributive Adjective"],
+  ["ADJD","ADJD", "Predicative Adjective"],
+  ["ADV","ADV", "Adverb"],
+  ["APPO","APPO", "Postposition"],
+  ["APPR","APPR", "Preposition"],
+  ["APPRART","APPRART", "Preposition with Determiner"],
+  ["APZR","APZR","Right Circumposition"],
+  ["ART","ART", "Determiner"],
+  ["CARD","CARD", "Cardinal Number"],
+  ["FM","FM", "Foreign Material"],
+  ["ITJ","ITJ", "Interjection"],
+  ["KOKOM","KOKOM", "Comparison Particle"],
+  ["KON","KON", "Coordinating Conjuncion"],
+  ["KOUI","KOUI", "Subordinating Conjunction with 'zu'"],
+  ["KOUS","KOUS", "Subordinating Conjunction with Sentence"],
+  ["NE","NE", "Named Entity"],
+  ["NN","NN", "Normal Nomina"],
+  ["PAV", "PAV", "Pronominal Adverb"],
+  ["PDAT","PDAT","Attributive Demonstrative Pronoun"],
+  ["PDS","PDS", "Substitutive Demonstrative Pronoun"],
+  ["PIAT","PIAT", "Attributive Indefinite Pronoun without Determiner"],
+  ["PIDAT","PIDAT", "Attributive Indefinite Pronoun with Determiner"],
+  ["PIS","PIS", "Substitutive Indefinite Pronoun"],
+  ["PPER","PPER", "Personal Pronoun"],
+  ["PPOSAT","PPOSAT", "Attributive Possessive Pronoun"],
+  ["PPOSS","PPOSS", "Substitutive Possessive Pronoun"],
+  ["PRELAT","PRELAT", "Attributive Relative Pronoun"],
+  ["PRELS","PRELS", "Substitutive Relative Pronoun"],
+  ["PRF","PRF", "Reflexive Pronoun"],
+  ["PROAV","PROAV", "Pronominal Adverb"],
+  ["PTKA","PTKA","Particle with Adjective"],
+  ["PTKANT","PTKANT", "Answering Particle"],
+  ["PTKNEG","PTKNEG", "Negation Particle"],
+  ["PTKVZ","PTKVZ", "Separated Verbal Particle"],
+  ["PTKZU","PTKZU", "'zu' Particle"],
+  ["PWAT","PWAT", "Attributive Interrogative Pronoun"],
+  ["PWAV","PWAV", "Adverbial Interrogative Pronoun"],
+  ["PWS","PWS", "Substitutive Interrogative Pronoun"],
+  ["TRUNC","TRUNC","Truncated"],
+  ["VAFIN","VAFIN", "Auxiliary Finite Verb"],
+  ["VAINF","VAINF", "Auxiliary Infinite Verb"],
+  ["VAIMP","VAIMP", "Auxiliary Finite Imperative Verb"],
+  ["VAPP","VAPP", "Auxiliary Perfect Participle"],
+  ["VMFIN","VMFIN", "Modal Finite Verb"],
+  ["VMINF","VMINF", "Modal Infinite Verb"],
+  ["VMPP","VMPP", "Modal Perfect Participle"],
+  ["VVFIN","VVFIN","Finite Verb"],
+  ["VVIMP","VVIMP", "Finite Imperative Verb"],
+  ["VVINF","VVINF", "Infinite Verb"],
+  ["VVIZU","VVIZU", "Infinite Verb with 'zu'"],
+  ["VVPP","VVPP", "Perfect Participle"],
+  ["XY", "XY", "Non-Word"]
+];
 
-  // Show hint table
-  this.show = function (topic) {
-    if (!hints[topic])
-      return;
-    this.hide();
-    this.active = -1;
-    this.list(hints[topic]);
-    var searchRight = search.getBoundingClientRect().right;
-    var infoRight = hint.getBoundingClientRect().right;
-    if (infoRight > searchRight) {
-      hint.style.marginLeft = '-' + (infoRight - searchRight) + 'px';
-    };
-    hint.style.opacity = 1;
-  };
+var mateSttsArray = sttsArray.slice(0);
+mateSttsArray.push(
+  ["<root-POS>","<root-POS>","Root Part of Speech"]
+);
 
-  // Initialize the mirror element
-  function init () {
-    // Copy input style
-    var searchRect = search.getBoundingClientRect();
-    var searchStyle = window.getComputedStyle(search, null);
 
-    with (mirror.style) {
-      left = searchRect.left + "px";
-      top = searchRect.bottom + "px";
-      borderLeftColor = "transparent";
-      paddingLeft     = searchStyle.getPropertyValue("padding-left");
-      marginLeft      = searchStyle.getPropertyValue("margin-left");
-      borderLeftWidth = searchStyle.getPropertyValue("border-left-width");
-      borderLeftStyle = searchStyle.getPropertyValue("border-left-style");
-      fontSize        = searchStyle.getPropertyValue("font-size");
-      fontFamily      = searchStyle.getPropertyValue("font-family");
+var hintArray = {
+  "-" : [
+    ["Connexor",   "cnx/"],
+    ["CoreNLP",    "corenlp/"],
+    ["Mate",       "mate/"],
+    ["OpenNLP",    "opennlp/"],
+    ["TreeTagger", "tt/"],
+    ["Xerox Parser", "xip/"]
+  ],
+  "corenlp/" : [
+    ["Named Entity", "ne=" , "Combined"],
+    ["Named Entity", "ne_dewac_175m_600=" , "ne_dewac_175m_600"],
+    ["Named Entity", "ne_hgc_175m_600=",    "ne_hgc_175m_600"]
+  ],
+  "corenlp/ne=" : namedEntities,
+  "corenlp/ne_dewac_175m_600=" : namedEntities,
+  "corenlp/ne_hgc_175m_600=" : namedEntities,
+  "cnx/" : [
+    ["Constituency", "c="],
+    ["Lemma", "l="],
+    ["Morphology", "m="],
+    ["Part-of-Speech", "p="],
+    ["Syntax", "syn="]
+  ],
+  "cnx/c=" : [
+    ["np", "np", "Nominal Phrase"]
+  ],
+  // http://www.ids-mannheim.de/cosmas2/projekt/referenz/connexor/morph.html
+  "cnx/m=" : [
+    ["Abbr","Abbr", "Nouns: Abbreviation"],
+    ["CMP","CMP", "Adjective: Comparative"],
+    ["IMP", "IMP", "Mood: Imperative"],
+    ["IND", "IND", "Mood: Indicative"],
+    ["INF", "INF", "Infinitive"],
+    ["ORD","ORD", "Numeral: Ordinal"],
+    ["PAST", "PAST", "Tense: past"],
+    ["PCP", "PCP", "Participle"],
+    ["PERF", "PERF", "Perfective Participle"],
+    ["PL","PL", "Nouns: Plural"],
+    ["PRES", "PRES", "Tense: present"],
+    ["PROG", "PROG", "Progressive Participle"],
+    ["Prop","Prop", "Nouns: Proper Noun"],
+    ["SUB", "SUB", "Mood: Subjunctive"],
+    ["SUP","SUP", "Adjective: Superlative"]
+  ],
+  // http://www.ids-mannheim.de/cosmas2/projekt/referenz/connexor/morph.html
+  "cnx/p=" : [
+    ["A", "A", "Adjective"],
+    ["ADV", "ADV", "Adverb"],
+    ["CC", "CC", "Coordination Marker"],
+    ["CS", "CS", "Clause Marker"],
+    ["DET", "DET", "Determiner"],
+    ["INTERJ", "INTERJ", "Interjection"],
+    ["N", "N", "Noun"],
+    ["NUM", "NUM", "Numeral"],
+    ["PREP", "PREP", "Preposition"],
+    ["PRON", "PRON", "Pro-Nominal"],
+    ["V", "V", "Verb"]
+  ],
+  // http://www.ids-mannheim.de/cosmas2/projekt/referenz/connexor/syntax.html
+  "cnx/syn=" : [
+    ["@ADVL", "@ADVL", "Adverbial Head"],
+    ["@AUX", "@AUX", "Auxiliary Verb"],
+    ["@CC", "@CC", "Coordination"]
+    ["@MAIN", "@MAIN", "Main Verb"],
+    ["@NH", "@NH", "Nominal Head"],
+    ["@POSTMOD", "@POSTMOD", "Postmodifier"],
+    ["@PREMARK", "@PREMARK", "Preposed Marker"],
+    ["@PREMOD", "@POSTMOD", "Premodifier"]
+  ],
+  "opennlp/" : [
+    ["Part-of-Speech", "p="]
+  ],
+  "opennlp/p=" : sttsArray,
+  "xip/" : [
+    ["Constituency", "c="],
+    // Inactive: ["Dependency", "d="],
+    ["Lemma", "l="],
+    ["Part-of-Speech", "p="],
+  ],
+  // "xip/c=" : [],
+  // Inactive: "xip/d=" : [],
+  // "xip/p=" : [],
+  "tt/" : [
+    ["Lemma", "l="],
+    ["Part-of-Speech", "p="]
+  ],
+  "tt/p=" : sttsArray,
+  "mate/" : [
+    // Inactive: "d" : ["d=", "Dependency"],
+    ["Lemma", "l="],
+    ["Morphology", "m="],
+    ["Part-of-Speech", "p="]
+  ],
+  // Inactive: mate/d=
+  "mate/p=" : mateSttsArray,
+  "mate/m=" : [
+    ["Case", "case:"],
+    ["Degree", "degree:"],
+    ["Gender", "gender:"],
+    ["Mood", "mood:"],
+    ["Number", "number:"],
+    ["Person", "person:"],
+    ["Tense","tense:"],
+    ["No type", "<no-type>"]
+  ],
+  "mate/m=case:" : [
+    ["acc", "acc", "Accusative"],
+    ["dat","dat", "Dative"],
+    ["gen", "gen","Genitive"],
+    ["nom","nom", "Nominative"],
+    ["*","*", "Undefined"]
+  ],
+  "mate/m=degree:" : [
+    ["comp","comp", "Comparative"],
+    ["pos","pos", "Positive"],
+    ["sup","sup", "Superative"]
+  ],
+  "mate/m=gender:" : [
+    ["fem", "fem", "Feminium"],
+    ["masc", "masc", "Masculinum"],
+    ["neut","neut", "Neuter"],
+    ["*","*","Undefined"]
+  ],
+  "mate/m=mood:" : [
+    ["imp","imp", "Imperative"],
+    ["ind","ind", "Indicative"],
+    ["subj","subj", "Subjunctive"]
+  ],
+  "mate/m=number:" : [
+    ["pl","pl","Plural"],
+    ["sg","sg","Singular"],
+    ["*","*","Undefined"]
+  ],
+  "mate/m=person:" : [
+    ["1","1", "First Person"],
+    ["2","2", "Second Person"],
+    ["3","3", "Third Person"]
+  ],
+  "mate/m=tense:" : [
+    ["past","past", "Past"],
+    ["pres","pres", "Present"]
+  ]
+};
+
+
+/**
+ * Analyze strings for prefixes
+ */
+var PrefixAnalyzer = {
+  _regex : new RegExp(
+    "(?:^|[^a-zA-Z0-9])" +    // Anchor
+    "((?:[-a-zA-Z0-9]+?)\/" + // Foundry
+    "(?:" +
+    "(?:[^=:]+?)=" +          // Layer
+    "(?:(?:[^:]+?):)?" +      // Key
+    ")?" +
+    ")$"),
+  analyze : function (text) {
+    if (!this._regex.exec(text))
+      return undefined;
+    return RegExp.$1
+  }
+};
+
+/**
+ * Event handling after a key pressed
+ */
+function updateKey (that, e) {
+  var menu = that.menu();
+  switch (e.key) {
+  case 'Esc':
+    // Hide menu
+    menu.hide();
+    break;
+  case 'Down':
+    e.halt(); // No event propagation
+
+    // Menu is not active
+    if (!menu.active) {
+      that.popUp()
+    }
+    // Menu is active
+    else {
+      that.removePrefix();
+      menu.next();
     };
 
-    qlSelect();
-  };
+    break;
+  case "Up":
+    if (!menu.active)
+      break;
+    e.halt(); // No event propagation
+    that.removePrefix();
+    menu.prev();
+    break;
+  case "Enter":
+    if (!menu.active)
+      break;
+    e.halt(); // No event propagation
+    that.insertText(menu.getActiveItem().getAction());
+    that.removePrefix();
 
-  // Hide hint table
-  this.hide = function () {
-    if (this.active === -2)
-      return;
-    this.active = -2;
-    hint.style.opacity = 0;
-    hint.style.marginLeft = 0;
+    // Remove menu
+    menu.hide();
 
-    // Remove all children
-    var lis = hint.childNodes;
-    for (var li = lis.length - 1; li >= 0; li--) {
-      hint.removeChild(lis[li])
+    // Fill this with the correct value
+    // Todo: This is redundant with click function
+    var show;
+    if ((show = that.analyzeContext()) !== "-") {
+      menu.show(show);
+      menu.update(
+        e.target.getBoundingClientRect().right
+      );
     };
-  };
 
-  // List elements in the hint table
-  this.list = function (hintList) {
-    var li, title;
-    var arrayType = hintList instanceof Array;
-    for (var i in hintList) {
-      // Create list items
-      li = document.createElement("li");
-      li.setAttribute("data-action", arrayType ? hintList[i] : hintList[i][0]);
-      title = document.createElement("strong");
-      title.appendChild(document.createTextNode(arrayType ? hintList[i] : i));
-      li.appendChild(title);
-      hint.appendChild(li);
+    break;
+  default:
+    if (!menu.active)
+      break;
 
-      // Include descriptions
-      if (!arrayType && hintList[i][1]) {
-        var desc = document.createElement("span");
-        desc.appendChild(document.createTextNode(hintList[i][1]));
-        li.appendChild(desc);
+    // key stroke is not a character
+    if (e.key.length != 1) {
+
+      // Key stroke is not a text modifying key
+      if (e.key !== 'Shift' &&
+          e.key !== 'Up'    &&
+          e.key !== 'Down'  &&
+          e.key !== 'Enter' &&
+	  e.key !== 'Alt'   &&
+	  e.key !== 'AltGraph' &&
+	  e.key !== 'CapsLock') {
+	that.insertPrefix();
+	menu.hide();
       };
-    };
-  };
-
-  // Choose next item in list
-  this.next = function () {
-    if (this.active === -2)
-      return false;
-
-    var lis = hint.getElementsByTagName("li");
-    if (this.active === -1) {
-      lis[0].setAttribute("class", "active");
-      this.active = 0;
-    }
-    else if (this.active === lis.length - 1) {
-      lis[this.active].removeAttribute("class");
-      lis[0].setAttribute("class", "active");
-      this.active = 0;
-    }
-    else {
-      lis[this.active].removeAttribute("class");
-      lis[++this.active].setAttribute("class", "active");
+      break;
     };
 
-    return true;
-  };
+    e.halt(); // No event propagation
+    
+    // Try to identify prefix
+    if (menu.skipToPrefix(e.key))
+      break;
 
-  // Choose previous item in list
-  this.previous = function () {
-    if (this.active === -2)
+    // Prefix not found
+    that.insertPrefix();
+    menu.hide();
+  };
+};
+
+// new hint object
+var Hint = {
+  _search   : undefined,   // Return the search element
+  _mirror   : undefined,   // Return the mirror element
+  _menu     : undefined,
+  _analyzer : undefined,
+  firstTry  : true,
+  menu : function () {
+    // In case this wasn't defined yet
+    if (this._menu === undefined) {
+      this._menu = Object.create(Menu).init();
+      this._mirror.appendChild(this._menu.getElement());
+    };
+    return this._menu;
+  },
+
+  // Initialize the object
+  init : function () {
+    this._search = document.getElementById("q-field");
+    this._mirror = document.createElement("div");
+    this._mirror.setAttribute("id", "searchMirror");
+    this._mirror.appendChild(document.createElement("span"));
+    document.getElementsByTagName("body")[0].appendChild(this._mirror);
+
+    this._analyzer = Object.create(PrefixAnalyzer);
+
+    // Update positional information, in case the windows size changes    
+    var that = this;
+    window.onresize = function () { that.reposition() };
+
+    // Add event listener for key pressed down
+    this._search.addEventListener(
+      "keydown",
+      function (e) {
+	updateKey(that, e)
+      },
+      false
+    );
+
+    // Reposition the mirror
+    this.reposition();
+
+    // Return object for chaining
+    return this;
+  },
+
+  // Popup method
+  popUp : function () {
+    if (this.active)
       return;
 
-    var lis = hint.getElementsByTagName("li");
-    if (this.active === -1) {
-      this.active = lis.length - 1;
-      lis[this.active].setAttribute("class", "active");
-    }
-    else if (this.active === 0) {
-      lis[0].removeAttribute("class");
-      this.active = lis.length - 1;
-      lis[this.active].setAttribute("class", "active");
-    }
-    else {
-      lis[this.active].removeAttribute("class");
-      lis[--this.active].setAttribute("class", "active");
-    };
-  };
+    // Reposition hint list on first try
+    if (this.firstTry)
+      this.reposition().firstTry = false;
 
-  // Choose item from list
-  this.choose = function () {
-    if (this.active < 0)
-      return;
+    // Update view
+    this.update();
 
-    var action = hint.getElementsByTagName("li")[this.active].getAttribute("data-action");
+    // Fill this with the correct value
+    if (this.menu().show(this.analyzeContext()))
+      this.update(
+        this._search.getBoundingClientRect().right
+      );
+    else
+      this.hide();
 
-    var value = search.value;
-    var start = search.selectionStart;
+    this._search.focus();
+  },
+
+  // Reposition the mirror object
+  reposition : function () {
+
+    // Update style properties
+    var searchRect  = this._search.getBoundingClientRect();
+    var searchStyle = window.getComputedStyle(this._search, null);
+    var mStyle = this._mirror.style;
+    mStyle.left = searchRect.left + "px";
+    mStyle.top  = searchRect.bottom + "px";
+    mStyle.borderLeftColor = "transparent";
+    mStyle.height          = "1px";
+    mStyle.paddingLeft     = searchStyle.getPropertyValue("padding-left");
+    mStyle.marginLeft      = searchStyle.getPropertyValue("margin-left");
+    mStyle.borderLeftWidth = searchStyle.getPropertyValue("border-left-width");
+    mStyle.borderLeftStyle = searchStyle.getPropertyValue("border-left-style");
+    mStyle.fontSize        = searchStyle.getPropertyValue("font-size");
+    mStyle.fontFamily      = searchStyle.getPropertyValue("font-family");
+    return this;
+  },
+
+  // Reposition the menu object
+  update : function () {
+    var s = this._search;
+    var start = s.selectionStart;
+    this._mirror.firstChild.textContent = s.value.substring(0, start);
+  },
+
+  analyzeContext : function () {
+    var context = this._splitInputText()[0];
+    if (context === undefined || context.length === 0)
+      return "-";
+    context = this._analyzer.analyze(context);
+    if (context === undefined || context.length === 0)
+      return "-";
+
+    return context;
+  },
+
+  _splitInputText : function () {
+    var s = this._search;
+    var value = s.value;
+    var start = s.selectionStart;
     var begin = value.substring(0, start);
-    var end = value.substring(start, value.length);
-    search.value = begin + action + end;
-    search.selectionStart = (begin + action).length
-    search.selectionEnd = search.selectionStart;
+    var end   = value.substring(start, value.length);
+    return new Array(begin, end);
+  },
 
-    this.hide();
+  // Insert text at the current cursor position
+  insertText : function (text) {
+    var s = this._search;
+    var splitText = this._splitInputText();
+    s.value          = splitText[0] + text + splitText[1];
+    s.selectionStart = (splitText[0] + text).length
+    s.selectionEnd   = s.selectionStart;
+    this._mirror.firstChild.textContent = splitText[0] + text;
+  },
 
-    // Check for new changes
-    mirror.firstChild.textContent = begin + action;
+  // Remove stored prefix 
+  removePrefix : function () {
+    this.menu()._prefix = undefined;
+  },
 
-    if (foundryRegex.exec(begin + action))
-      this.show(RegExp.$1 + (RegExp.$2 ? '/' + RegExp.$2 : ''));
-
-    return true;
-  };
-
-  function changed (e) {
-    var el = e.target;
-    if (e.key === '/' || e.key === '=') {
-      var start = el.selectionStart;
-      mirror.firstChild.textContent = el.value.substring(0, start);
-      var sub = el.value.substring(start - 128 >= 0 ? start - 128 : 0, start);
-
-      if (foundryRegex.exec(sub))
-	that.show(RegExp.$1 + (RegExp.$2 ? '/' + RegExp.$2 : ''));
-    }
-    else if (e.key === '>') {
-      that.hide();
-    }
-    else if (ql === 'poliqarp' && (e.key === '[' || e.key === '<')) {
-      mirror.firstChild.textContent = el.value.substring(0, el.selectionStart);
-      that.show("-foundries");
-    }
-    else if (e.key !== 'Shift' &&
-             e.key !== 'Up'    &&
-             e.key !== 'Down'  &&
-             e.key !== 'Enter' &&
-	     e.key !== 'Alt'   &&
-	     e.key !== 'AltGraph' &&
-	     e.key !== 'CapsLock') {
-      that.hide();
-    };
-  };
-
-  // Select item from suggestion list
-  function select (e) {
-    if (that.active === -2)
+  // Insert stored prefix at current cursor position
+  insertPrefix : function () {
+    if (this.menu()._prefix === undefined)
       return;
-    if (e.key === 'Down') {
-      e.stopPropagation();
-      e.preventDefault();
-      return that.next();
-    }
-    else if (e.key === 'Up') {
-      e.stopPropagation();
-      e.preventDefault();
-      return that.previous();
-    }
-    else if (e.key === 'Enter' && that.choose()) {
-      e.stopPropagation();
-      e.preventDefault();
-      return false;
-    }
-    else if (e.key !== 'Shift' &&
-	     e.key !== 'Alt' &&
-	     e.key !== 'AltGraph' &&
-	     e.key !== 'CapsLock') {
-      that.hide();
-    };
-    return true;
-  };
+    this.insertText(this.menu()._prefix);
+  }
+};
 
-  function qlSelect () {
-    var nodes = qlField.childNodes;
-    for (var i = 0; i < nodes.length; i++) {
-      if (nodes[i].selected) {
-        ql = nodes[i].value;
-        break;
+
+/**
+* Menu list
+*/
+var Menu = {
+  active     : false,
+  _element   : undefined,
+  _position  : 0,   // Position of menu item
+  _offset    : 0,   // Visual offset for chosen highlight
+  _size      : 8,  // Number of items to be shown
+  _items     : [],  // Items for menu
+  _name      : undefined,
+  _prefix    : undefined,
+  getElement : function () {
+    return this._element;
+  },
+  init : function () {
+    this._element = document.createElement("ul");
+
+    // Add onclick event
+    this._element.addEventListener("click", chooseHint, false);
+
+    this._element.style.opacity = 0;
+    this.active = false;
+    this._setDefaults();
+    return this;
+  },
+  update : function (searchRightPosition) {
+    var infoRightPosition = this._element.getBoundingClientRect().right;
+    if (infoRightPosition > searchRightPosition) {
+      this._element.style.marginLeft = '-' + (infoRightPosition - searchRightPosition) + 'px';
+    };
+    return this;
+  },
+  next : function () {
+    if (!this.active)
+      return;
+    this._clearView();
+    this._position++;
+
+    // In case the list is bigger than the view
+    if (this._items.length > this._size) {
+      if (this._position >= this._items.length) {
+	// Roll to top
+	this._offset = 0;
+	this._position = 0;
+	this._showItems(0);
+      }
+      else if (this._position >= (this._size + this._offset)) {
+	// Roll down
+	this._element.removeChild(this._element.firstChild);
+	this._offset++;
+	this._element.appendChild(this.getItem(this._position).getElement());
+      };
+    }
+    else if (this._position >= this._items.length) {
+      this._position = 0;
+    };
+    this._updateView();
+  },
+  prev : function () {
+    if (!this.active)
+      return;
+    this._clearView();
+    this._position--;
+
+    // In case the list is bigger than the view
+    if (this._items.length > this._size) {
+      if (this._position < 0) {
+	// roll to bottom
+	this._setToLast();
+	this._offset = (this._position - this._size) + 1;
+	this._showLastItems();
+      }
+      else if (this._position < this._offset) {
+	// roll up
+	this._element.removeChild(this._element.lastChild);
+	this._offset--;
+	this._element.insertBefore(
+	  this.getItem(this._position).getElement(),
+	  this._element.firstChild
+	);
+      };
+    }
+    else if (this._position < 0) {
+      this._setToLast();
+    };
+    this._updateView();
+  },
+  skipToPrefix : function (prefix) {
+    if (this._prefix === undefined)
+      this._prefix = prefix.toLocaleLowerCase();
+    else
+      this._prefix += prefix.toLocaleLowerCase();
+
+    var pos = 0;
+    var found = false;
+    var good = -1;
+    var test;
+    for (; pos < this._items.length; pos++) {
+      if ((test = this.getItem(pos).getLCName().indexOf(this._prefix)) !== -1) {
+	if (test === 0) {
+	  found = true;
+	  break;
+	};
+	good = pos;
       };
     };
+
+    // Perfect prefix
+    if (found)
+      return this.skipToPos(pos);
+    // At least infix
+    else if (good !== -1)
+      return this.skipToPos(good);
+    // No
+    return false;
+  },
+  skipToPos : function (index) {
+    if (!this.active)
+      return false;
+    if (index < 0 || index >= this._items.length)
+      return false;
+
+    this._clearView();
+    this._position = index;
+
+    if (index < this._offset || index >= (this._offset + this._size)) {
+
+      // Index is in the final frame
+      if (index >= (this._items.length - this._size)) {
+	this._offset = this._items.length - this._size;
+	this._showLastItems();
+      }
+
+      // Index is in the final frame
+      else {
+	this._offset = index;
+	this._showItems(index);
+      };
+    };
+
+    // Activate new position
+    this._updateView();
+    return true;
+  },
+  show : function (name) {
+    // The element is already given
+    if (this._name !== name) {
+
+      // Todo: store hints in hash
+
+      // Delete items
+      this._items.length = 0;
+
+      var items = hintArray[name];
+
+      // Hints not found
+      if (items === undefined)
+	return false;
+
+      var i;
+      for (i in items) {
+	var item = Object.create(MenuItem).init(items[i]);
+	this._items.push(item);
+      };
+
+      // Add classes for rolling menus
+      this.getItem(0).getElement().classList.add("no-more");
+      this.getItem(i).getElement().classList.add("no-more");
+
+      this._name = name;
+    };
+    this._showItems(0);
+    this._element.style.opacity = 1;
+    this._setDefaults();
+    this.active = true;
+    this._updateView();
+    return true;
+  },
+  hide : function () {
+    this._element.style.opacity = 0;
+    if (this.active)
+      this.getActiveItem().deactivate();
+    this._setDefaults();
+    this.active = false;
+  },
+  getActiveItem : function () {
+    return this._items[this._position];
+  },
+  getItem : function (index) {
+    return this._items[index];
+  },
+  getPrefix : function () {
+    return this._prefix;
+  },
+  _setDefaults : function () {
+    this._offset = 0;
+    this._position = 0;
+    this._prefix = undefined;
+  },
+  // Remove all visible list items
+  _deleteMenu : function () {
+    var child;
+    while (child = this._element.firstChild)
+      this._element.removeChild(child);
+  },
+  _clearView : function () {
+    var active = this.getActiveItem();
+    if (active !== undefined)
+      active.deactivate();
+  },
+  _updateView : function () {
+    var active = this.getActiveItem();
+    if (active !== undefined)
+      active.activate();
+  },
+
+  // Make all list items visible starting at a certain offset
+  _showItems : function (offset) {
+    this._deleteMenu();
+    for (var i = offset; i < this._size + offset; i++) {
+      if (i >= this._items.length)
+	break;
+      this._element.appendChild(
+	this._items[i].getElement()
+      )
+    };
+  },
+
+  // Make all final list items visible
+  _showLastItems : function () {
+    this._deleteMenu();
+    for (var i = (this._items.length - 1); i >= (this._items.length - this._size); i--) {
+      if (i < 0)
+	break;
+      if (!this._element.firstChild)
+	this._element.appendChild(this._items[i].getElement());
+      else
+	this._element.insertBefore(
+	  this._items[i].getElement(),
+	  this._element.firstChild
+	);
+    };
+  },
+  _setToLast : function () {
+    this._position = this._items.length - 1;
+  }
+};
+
+function chooseHint (e) {
+  var element = e.target;
+  while (element.nodeName == "STRONG" || element.nodeName == "SPAN") {
+    element = element.parentNode;
   };
 
-  // Initialize style
-  init();
-  window.onresize = init;
-  search.addEventListener("keyup",   changed,  false);
-  search.addEventListener("keydown", select,   false);
-  qlField.addEventListener("change", qlSelect, false);
+  if (element === undefined || element.nodeName != "LI")
+    return;
+
+  var action = element.getAttribute('data-action');
+  hint.insertText(action);
+  var menu = hint.menu();
+  menu.hide();
+
+  // Fill this with the correct value
+  var show;
+  if ((show = hint.analyzeContext()) !== "-") {
+    menu.show(show);
+    menu.update(
+      hint._search.getBoundingClientRect().right
+    );
+  };
+
+  hint._search.focus();
+};
+
+var MenuItem = {
+  _name    : undefined,
+  _lcname  : undefined,
+  _desc    : undefined,
+  _element : undefined,
+  _action  : "",
+  activate : function () {
+    this._element.classList.add("active");
+  },
+  deactivate : function () {
+    this._element.classList.remove("active");
+  },
+  // Initialize this item
+  init : function (param) {
+    this._name = param[0];
+    this._action = param[1];
+    this._lcname = this._name.toLocaleLowerCase();
+
+    if (param.length > 2) {
+      this._desc = param[2];
+      this._lcname += " " + this._desc.toLocaleLowerCase();
+    };
+
+    return this;
+  },
+
+  // Created element of this item
+  getElement : function () {
+    if (this._element !== undefined)
+      return this._element;
+
+    var li = document.createElement("li");
+
+    li.setAttribute("data-action", this._action);
+
+    var name = document.createElement("strong");
+
+    name.appendChild(document.createTextNode(this._name));
+    li.appendChild(name);
+    if (this._desc !== undefined) {
+      var desc = document.createElement("span");
+      desc.appendChild(document.createTextNode(this._desc));
+      li.appendChild(desc);
+    };
+    this._element = li;
+    return this._element;
+  },
+
+  // Name of this item
+  getName : function () {
+    return this._name;
+  },
+
+  getLCName : function () {
+    return this._lcname;
+  },
+
+  // Description of this item
+  getDesc : function () {
+    return this._desc;
+  },
+
+
+  getAction : function () {
+    return this._action;
+  }
 };