Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.617
diff -u -d -F^\s*function -r1.617 common.inc
--- includes/common.inc	15 Feb 2007 11:40:17 -0000	1.617
+++ includes/common.inc	6 Mar 2007 14:45:01 -0000
@@ -1753,6 +1753,24 @@ function drupal_to_js($var) {
 }
 
 /**
+ * Outputs a properly structured autocomplete object and sets the right header.
+ *
+ * @param $matches
+ *   An indexed array with the items found for the keyword. The value of the
+ *   textfield will be replaced with the array's key when an item is selected.
+ *   The value is shown to the user.
+ * @param $info
+ *   (Optional) If set, the info text will be displayed together with the
+ *   items, but will not be selectable.
+ */
+function drupal_autocomplete($matches, $info = '') {
+  // Set the correct content type for JSON output.
+  drupal_set_header('Content-Type: text/javascript; charset=utf-8');
+
+  echo drupal_to_js(array('matches' => $matches, 'info' => theme('autocomplete_info', $info)));
+}
+
+/**
  * Wrapper around urlencode() which avoids Apache quirks.
  *
  * Should be used when placing arbitrary data in an URL. Note that Drupal paths
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.179
diff -u -d -F^\s*function -r1.179 form.inc
--- includes/form.inc	27 Feb 2007 12:45:13 -0000	1.179
+++ includes/form.inc	6 Mar 2007 14:45:04 -0000
@@ -876,6 +876,14 @@ function _form_set_value(&$form_values, 
 }
 
 /**
+ * Handles autocomplete functionality for an element.
+ */
+function form_autocomplete($element) {
+  drupal_add_js('misc/autocomplete.js');
+  drupal_add_js(array('autocomplete' => array($element['#id'] => url($element['#autocomplete_path']))), 'setting');
+}
+
+/**
  * Retrieve the default properties for the defined element type.
  */
 function _element_info($type, $refresh = NULL) {
@@ -1411,13 +1419,11 @@ function theme_token($element) {
 function theme_textfield($element) {
   $size = $element['#size'] ? ' size="' . $element['#size'] . '"' : '';
   $class = array('form-text');
-  $extra = '';
   $output = '';
 
   if ($element['#autocomplete_path']) {
-    drupal_add_js('misc/autocomplete.js');
+    form_autocomplete($element);
     $class[] = 'form-autocomplete';
-    $extra =  '<input class="autocomplete" type="hidden" id="'. $element['#id'] .'-autocomplete" value="'. check_url(url($element['#autocomplete_path'], array('absolute' => TRUE))) .'" disabled="disabled" />';
   }
   _form_set_class($element, $class);
 
@@ -1431,7 +1437,7 @@ function theme_textfield($element) {
     $output .= ' <span class="field-suffix">'. $element['#field_suffix'] .'</span>';
   }
 
-  return theme('form_element', $element, $output). $extra;
+  return theme('form_element', $element, $output);
 }
 
 /**
Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.343
diff -u -d -F^\s*function -r1.343 theme.inc
--- includes/theme.inc	2 Mar 2007 09:40:13 -0000	1.343
+++ includes/theme.inc	6 Mar 2007 14:45:06 -0000
@@ -1122,6 +1122,22 @@ function theme_progress_bar($percent, $m
   return $output;
 }
 
+function theme_autocomplete_item($primary, $secondary = NULL, $description = NULL) {
+  $output .= '<div class="primary">'. $primary .'</div>';
+  if (!empty($secondary)) {
+    $output .= '<div class="secondary">'. $secondary .'</div>';
+  }
+  if (!empty($description)) {
+    $output .= '<small>'. $description .'</small>';
+  }
+  return $output;
+}
+
+function theme_autocomplete_info($info) {
+  if (!empty($info)) {
+    return '<div class="info">'. $info .'</div>';
+  }
+}
 /**
  * @} End of "defgroup themeable".
  */
Index: misc/autocomplete.js
===================================================================
RCS file: /cvs/drupal/drupal/misc/autocomplete.js,v
retrieving revision 1.17
diff -u -d -F^\s*function -r1.17 autocomplete.js
--- misc/autocomplete.js	9 Jan 2007 07:31:04 -0000	1.17
+++ misc/autocomplete.js	6 Mar 2007 14:45:07 -0000
@@ -1,51 +1,55 @@
 // $Id: autocomplete.js,v 1.17 2007/01/09 07:31:04 drumm Exp $
 
-/**
- * Attaches the autocomplete behaviour to all required fields
- */
-Drupal.autocompleteAutoAttach = function () {
-  var acdb = [];
-  $('input.autocomplete').each(function () {
-    var uri = this.value;
-    if (!acdb[uri]) {
-      acdb[uri] = new Drupal.ACDB(uri);
-    }
-    var input = $('#' + this.id.substr(0, this.id.length - 13))
-      .attr('autocomplete', 'OFF')[0];
-    $(input.form).submit(Drupal.autocompleteSubmit);
-    new Drupal.jsAC(input, acdb[uri]);
-  });
-}
+Drupal.autocomplete = { handlers: {} };
 
 /**
  * Prevents the form from submitting if the suggestions popup is open
  * and closes the suggestions popup when doing so.
  */
-Drupal.autocompleteSubmit = function () {
+Drupal.autocomplete.submit = function () {
   return $('#autocomplete').each(function () {
     this.owner.hidePopup();
   }).size() == 0;
 }
 
 /**
+ * Attaches the autocomplete behaviour to all required fields
+ */
+Drupal.autocomplete.attach = function () {
+  if (!Drupal.settings || !Drupal.settings.autocomplete) return;
+
+  var handlers = Drupal.autocomplete.handlers;
+  for (id in Drupal.settings.autocomplete) {
+    var url = Drupal.settings.autocomplete[id];
+    if (!handlers[url]) {
+      handlers[url] = new Drupal.autocomplete.handler(url);
+    }
+
+    new Drupal.autocomplete.field($('#' + id)[0], handlers[url]);
+  }
+}
+
+/**
  * An AutoComplete object
  */
-Drupal.jsAC = function (input, db) {
+Drupal.autocomplete.field = function (input, db) {
   var ac = this;
   this.input = input;
   this.db = db;
 
+  $(this.input.form).submit(Drupal.autocomplete.submit);
+
   $(this.input)
+    .attr('autocomplete', 'OFF')
     .keydown(function (event) { return ac.onkeydown(this, event); })
     .keyup(function (event) { ac.onkeyup(this, event) })
     .blur(function () { ac.hidePopup(); ac.db.cancel(); });
-
 };
 
 /**
  * Handler for the "keydown" event
  */
-Drupal.jsAC.prototype.onkeydown = function (input, e) {
+Drupal.autocomplete.field.prototype.onkeydown = function (input, e) {
   if (!e) {
     e = window.event;
   }
@@ -64,7 +68,7 @@
 /**
  * Handler for the "keyup" event
  */
-Drupal.jsAC.prototype.onkeyup = function (input, e) {
+Drupal.autocomplete.field.prototype.onkeyup = function (input, e) {
   if (!e) {
     e = window.event;
   }
@@ -101,14 +105,17 @@
 /**
  * Puts the currently highlighted suggestion into the autocomplete field
  */
-Drupal.jsAC.prototype.select = function (node) {
+Drupal.autocomplete.field.prototype.select = function (node) {
   this.input.value = node.autocompleteValue;
+  if (this.input.selectionStart) {
+    this.input.selectionStart = this.input.value.length
+  }
 }
 
 /**
  * Highlights the next suggestion
  */
-Drupal.jsAC.prototype.selectDown = function () {
+Drupal.autocomplete.field.prototype.selectDown = function () {
   if (this.selected && this.selected.nextSibling) {
     this.highlight(this.selected.nextSibling);
   }
@@ -123,7 +130,7 @@
 /**
  * Highlights the previous suggestion
  */
-Drupal.jsAC.prototype.selectUp = function () {
+Drupal.autocomplete.field.prototype.selectUp = function () {
   if (this.selected && this.selected.previousSibling) {
     this.highlight(this.selected.previousSibling);
   }
@@ -132,7 +139,7 @@
 /**
  * Highlights a suggestion
  */
-Drupal.jsAC.prototype.highlight = function (node) {
+Drupal.autocomplete.field.prototype.highlight = function (node) {
   if (this.selected) {
     $(this.selected).removeClass('selected');
   }
@@ -143,7 +150,7 @@
 /**
  * Unhighlights a suggestion
  */
-Drupal.jsAC.prototype.unhighlight = function (node) {
+Drupal.autocomplete.field.prototype.unhighlight = function (node) {
   $(node).removeClass('selected');
   this.selected = false;
 }
@@ -151,10 +158,10 @@
 /**
  * Hides the autocomplete suggestions
  */
-Drupal.jsAC.prototype.hidePopup = function (keycode) {
+Drupal.autocomplete.field.prototype.hidePopup = function (keycode) {
   // Select item if the right key or mousebutton was pressed
   if (this.selected && ((keycode && keycode != 46 && keycode != 8 && keycode != 27) || !keycode)) {
-    this.input.value = this.selected.autocompleteValue;
+    this.select(this.selected);
   }
   // Hide popup
   var popup = this.popup;
@@ -168,20 +175,18 @@
 /**
  * Positions the suggestions popup and starts a search
  */
-Drupal.jsAC.prototype.populatePopup = function () {
+Drupal.autocomplete.field.prototype.populatePopup = function () {
   // Show popup
   if (this.popup) {
     $(this.popup).remove();
   }
   this.selected = false;
-  this.popup = document.createElement('div');
-  this.popup.id = 'autocomplete';
-  this.popup.owner = this;
-  $(this.popup).css({
+  this.popup = $('<div id="autocomplete"></div>').css({
     marginTop: this.input.offsetHeight +'px',
     width: (this.input.offsetWidth - 4) +'px',
     display: 'none'
   });
+  this.popup[0].owner = this;
   $(this.input).before(this.popup);
 
   // Do search
@@ -192,39 +197,42 @@
 /**
  * Fills the suggestion popup with any matches received
  */
-Drupal.jsAC.prototype.found = function (matches) {
+Drupal.autocomplete.field.prototype.found = function (matches) {
   // If no value in the textfield, do not show the popup.
   if (!this.input.value.length) {
     return false;
   }
 
   // Prepare matches
-  var ul = document.createElement('ul');
-  var ac = this;
-  for (key in matches) {
-    var li = document.createElement('li');
-    $(li)
-      .html('<div>'+ matches[key] +'</div>')
-      .mousedown(function () { ac.select(this); })
-      .mouseover(function () { ac.highlight(this); })
-      .mouseout(function () { ac.unhighlight(this); });
-    li.autocompleteValue = key;
-    $(ul).append(li);
+  var ul = $('<ul></ul>');
+  var field = this;
+  for (key in matches.matches) {
+    var li = $('<li class="clear-block"></li>')
+      .html(matches.matches[key])
+      .mouseover(function () { field.highlight(this); })
+      .mouseout(function () { field.unhighlight(this); });
+    li[0].autocompleteValue = key;
+    ul.append(li);
   }
 
   // Show popup with matches, if any
   if (this.popup) {
-    if (ul.childNodes.length > 0) {
-      $(this.popup).empty().append(ul).show();
+    this.popup.empty();
+    if ($(ul).children().size()) {
+      this.popup.append(ul).show();
     }
-    else {
-      $(this.popup).css({visibility: 'hidden'});
+    if (matches.info) {
+      this.popup.append($('<div></div>').html(matches.info)).show();
+    }
+
+    if (!$(this.popup).children().size()) {
+      this.popup.css({visibility: 'hidden'});
       this.hidePopup();
     }
   }
 }
 
-Drupal.jsAC.prototype.setStatus = function (status) {
+Drupal.autocomplete.field.prototype.setStatus = function (status) {
   switch (status) {
     case 'begin':
       $(this.input).addClass('throbbing');
@@ -240,7 +248,7 @@
 /**
  * An AutoComplete DataBase object
  */
-Drupal.ACDB = function (uri) {
+Drupal.autocomplete.handler = function (uri) {
   this.uri = uri;
   this.delay = 300;
   this.cache = {};
@@ -249,7 +257,7 @@
 /**
  * Performs a cached and delayed search
  */
-Drupal.ACDB.prototype.search = function (searchString) {
+Drupal.autocomplete.handler.prototype.search = function (searchString) {
   var db = this;
   this.searchString = searchString;
 
@@ -291,7 +299,7 @@
 /**
  * Cancels the current autocomplete request
  */
-Drupal.ACDB.prototype.cancel = function() {
+Drupal.autocomplete.handler.prototype.cancel = function() {
   if (this.owner) this.owner.setStatus('cancel');
   if (this.timer) clearTimeout(this.timer);
   this.searchString = '';
@@ -299,5 +307,5 @@
 
 // Global Killswitch
 if (Drupal.jsEnabled) {
-  $(document).ready(Drupal.autocompleteAutoAttach);
+  $(document).ready(Drupal.autocomplete.attach);
 }
Index: modules/profile/profile.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/profile/profile.module,v
retrieving revision 1.194
diff -u -d -F^\s*function -r1.194 profile.module
--- modules/profile/profile.module	11 Feb 2007 09:30:51 -0000	1.194
+++ modules/profile/profile.module	6 Mar 2007 14:45:10 -0000
@@ -705,16 +705,14 @@ function profile_form_profile($edit, $us
  * Callback to allow autocomplete of profile text fields.
  */
 function profile_autocomplete($field, $string) {
+  $matches = array();
   if (db_result(db_query("SELECT COUNT(*) FROM {profile_fields} WHERE fid = %d AND autocomplete = 1", $field))) {
-    $matches = array();
     $result = db_query_range("SELECT value FROM {profile_values} WHERE fid = %d AND LOWER(value) LIKE LOWER('%s%%') GROUP BY value ORDER BY value ASC", $field, $string, 0, 10);
     while ($data = db_fetch_object($result)) {
-      $matches[$data->value] = check_plain($data->value);
+      $matches[$data->value] = theme('autocomplete_item', check_plain($data->value));
     }
-
-    print drupal_to_js($matches);
   }
-  exit();
+  drupal_autocomplete($matches);
 }
 
 /**
Index: modules/system/system.css
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.css,v
retrieving revision 1.23
diff -u -d -F^\s*function -r1.23 system.css
--- modules/system/system.css	2 Mar 2007 09:40:13 -0000	1.23
+++ modules/system/system.css	6 Mar 2007 14:45:10 -0000
@@ -273,6 +273,10 @@
   border: 1px solid;
   overflow: hidden;
   z-index: 100;
+  background: #fff;
+  color: #000;
+  cursor: default;
+  line-height: normal;
 }
 #autocomplete ul {
   margin: 0;
@@ -280,15 +284,32 @@
   list-style: none;
 }
 #autocomplete li {
-  background: #fff;
-  color: #000;
-  white-space: pre;
-  cursor: default;
+  padding: 0.25em 0;
+  clear: both;
 }
 #autocomplete li.selected {
   background: #0072b9;
   color: #fff;
 }
+#autocomplete li .primary {
+  font-weight: bold;
+  float: left;
+}
+#autocomplete li .secondary {
+  float: right;
+}
+#autocomplete li small {
+  font-size: 0.85em;
+  color: #555;
+  display: block;
+  clear: both;
+}
+#autocomplete li.selected * {
+  color:#fff;
+}
+#autocomplete div.info {
+  padding:0.25em 0.5em;
+}
 /* Animated throbber */
 html.js input.form-autocomplete {
   background-image: url(../../misc/throbber.gif);
Index: modules/taxonomy/taxonomy.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v
retrieving revision 1.341
diff -u -d -F^\s*function -r1.341 taxonomy.module
--- modules/taxonomy/taxonomy.module	27 Feb 2007 12:48:33 -0000	1.341
+++ modules/taxonomy/taxonomy.module	6 Mar 2007 14:45:14 -0000
@@ -1475,24 +1475,35 @@ function taxonomy_autocomplete($vid, $st
   $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
   preg_match_all($regexp, $string, $matches);
   $array = $matches[1];
+  $matches = array();
+  $info = '';
 
   // Fetch last tag
   $last_string = trim(array_pop($array));
   if ($last_string != '') {
-    $result = db_query_range(db_rewrite_sql("SELECT t.tid, t.name FROM {term_data} t WHERE t.vid = %d AND LOWER(t.name) LIKE LOWER('%%%s%%')", 't', 'tid'), $vid, $last_string, 0, 10);
+    $result = pager_query(db_rewrite_sql("SELECT t.tid, t.name, t.description FROM {term_data} t WHERE t.vid = %d AND LOWER(t.name) LIKE LOWER('%%%s%%')", 't', 'tid'), 10, 'taxonomy_autocomplete', NULL, $vid, $last_string);
 
     $prefix = count($array) ? implode(', ', $array) .', ' : '';
 
-    $matches = array();
     while ($tag = db_fetch_object($result)) {
-      $n = $tag->name;
-      // Commas and quotes in terms are special cases, so encode 'em.
-      if (strpos($tag->name, ',') !== FALSE || strpos($tag->name, '"') !== FALSE) {
-        $n = '"'. str_replace('"', '""', $tag->name) .'"';
+      if (!in_array($tag->name, $array)) {
+        $id = $tag->name;
+        // Commas and quotes in terms are special cases, so encode them.
+        if (strpos($id, ',') !== FALSE || strpos($id, '"') !== FALSE) {
+          $n = '"'. str_replace('"', '""', $id) .'"';
+        }
+        $matches[$prefix . $id .', '] = theme('autocomplete_item', check_plain($tag->name), l(t('List'), 'taxonomy/term/'. $tag->tid), check_plain($tag->description));
       }
-      $matches[$prefix . $n] = check_plain($tag->name);
     }
-    print drupal_to_js($matches);
-    exit();
   }
+
+  global $pager_total_items;
+  if (count($matches) == 0) {
+    $info = t('There are no matches.');
+  }
+  elseif (isset($pager_total_items) && $pager_total_items['taxonomy_autocomplete'] > 10) {
+    $info = format_plural($pager_total_items['taxonomy_autocomplete'] - 10, 'There is one further match.', 'There are @count further matches.');
+  }
+
+  drupal_autocomplete($matches, $info);
 }
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.756
diff -u -d -F^\s*function -r1.756 user.module
--- modules/user/user.module	15 Feb 2007 11:40:18 -0000	1.756
+++ modules/user/user.module	6 Mar 2007 14:45:20 -0000
@@ -684,6 +684,13 @@ function theme_user_list($users, $title 
   return theme('item_list', $items, $title);
 }
 
+function theme_user_autocomplete_item($user) {
+  $output = '<div class="primary">'. check_plain($user->name) .'</div>';
+  $output .= '<div class="secondary">'. l('('. t('View profile') .')', 'user/'. $user->uid) .'</div>';
+  
+  return $output;
+}
+
 function user_is_anonymous() {
   return !$GLOBALS['user']->uid;
 }
@@ -2541,14 +2548,24 @@ function _user_forms(&$edit, $account, $
  */
 function user_autocomplete($string = '') {
   $matches = array();
+  $info = '';
   if ($string) {
-    $result = db_query_range("SELECT name FROM {users} WHERE LOWER(name) LIKE LOWER('%s%%')", $string, 0, 10);
+    $result = pager_query("SELECT uid FROM {users} WHERE LOWER(name) LIKE LOWER('%s%%')", 10, 'user_autocomplete', NULL, $string);
     while ($user = db_fetch_object($result)) {
-      $matches[$user->name] = check_plain($user->name);
-   }
+      $user = user_load($user->uid);
+      $matches[$user->name] = theme('user_autocomplete_item', $user);
+    }
   }
-  print drupal_to_js($matches);
-  exit();
+
+  global $pager_total_items;
+  if (count($matches) == 0) {
+    $info = t('No matching users were found.');
+  }
+  elseif (isset($pager_total_items) && $pager_total_items['user_autocomplete'] > 10) {
+    $info = format_plural($pager_total_items['user_autocomplete'] - 10, 'The ten best matches are listed. One further user was found.', 'The ten best matches are listed. @count further users were found.');
+  }
+
+  drupal_autocomplete($matches, $info);
 }
 
 /**
Index: themes/garland/style.css
===================================================================
RCS file: /cvs/drupal/drupal/themes/garland/style.css,v
retrieving revision 1.17
diff -u -d -F^\s*function -r1.17 style.css
--- themes/garland/style.css	2 Mar 2007 09:40:14 -0000	1.17
+++ themes/garland/style.css	6 Mar 2007 14:45:22 -0000
@@ -804,8 +804,9 @@
  */
 #autocomplete li {
   cursor: default;
-  padding: 2px;
+  padding: 0.25em;
   margin: 0;
+  background: none;
 }
 
 /**
