Index: modules/system/system.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v
retrieving revision 1.248
diff -u -r1.248 system.admin.inc
--- modules/system/system.admin.inc	9 Jan 2010 23:03:21 -0000	1.248
+++ modules/system/system.admin.inc	11 Jan 2010 02:16:29 -0000
@@ -958,6 +958,24 @@
   );
   $form['#action'] = url('admin/modules/list/confirm');
 
+  $js_settings = array(
+    'instantfilter' => array(
+      'system-modules' => array(
+        'label' => t('Filter modules'),
+        'description' => t('Enter the name of a module to filter the list.'),
+        'itemSelector' => 'tbody > tr',
+        'ignoreSelector' => '.admin-requirements, td.help, td.permissions, td.configure',
+        'groupSelector' => 'fieldset, table'
+      ),
+    ),
+  );
+
+  $form['#attached'] = array(
+    'js' => array(
+      array('data' => $js_settings, 'type' => 'setting'),
+    ),
+    'library' => array(array('system', 'instantfilter')),
+  );
   return $form;
 }
 
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.873
diff -u -r1.873 system.module
--- modules/system/system.module	10 Jan 2010 00:09:37 -0000	1.873
+++ modules/system/system.module	11 Jan 2010 02:16:29 -0000
@@ -1117,6 +1117,19 @@
     ),
   );
 
+  // Instant Filter.
+  $libraries['instantfilter'] = array(
+    'title' => 'Instant Filter',
+    'website' => 'http://drupal.org/node/396478',
+    'version' => '1.0',
+    'js' => array(
+      'misc/instantfilter.js' => array(),
+    ),
+    'css' => array(
+      'misc/instantfilter.css' => array(),
+    ),
+  );
+
   // Farbtastic.
   $libraries['farbtastic'] = array(
     'title' => 'Farbtastic',
Index: misc/instantfilter.js
===================================================================
RCS file: misc/instantfilter.js
diff -N misc/instantfilter.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ misc/instantfilter.js	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,239 @@
+// $Id$
+(function ($) {
+
+/**
+ * Attaches the instantfilter behavior.
+ */
+Drupal.behaviors.instantFilter = {
+  attach: function (context, settings) {
+    // Bind instantfilter behaviors specified in the settings.
+    for (var base in settings.instantfilter) {
+      var container = $('#' + base);
+      if (!container.hasClass('.instantfilter-processed')) {
+        container
+          .addClass('instantfilter-processed')
+          .data('instantfilter', new Drupal.instantFilter(container, settings.instantfilter[base]));
+      }
+    }
+
+    // Bind instantfilter behaviors to all items showing the class.
+    $('.instantfilter-container', context).once('instantfilter', function () {
+      $.data(this, 'instantfilter', new Drupal.instantFilter($(this), {}));
+    });
+
+    // If context has an parent with the instantfilter class, then it's added
+    // dynamicly (e.g. using AJAX). If so, the index needs to be rebuild.
+    $(context).closest('.instantfilter-processed').each(function () {
+      $(this).data('instantfilter').rebuildIndex();
+    });
+  }
+};
+
+/**
+ * The tableFilter object.
+ */
+Drupal.instantFilter = function (container, settings) {
+  this.settings = $.extend(Drupal.instantFilter.defaultSettings, settings);
+  this.container = (container.is('form') ? container.children('div') : container);
+
+  // Create the filter textfield.
+  this.createTextfield();
+};
+
+/**
+ * TODO
+ */
+Drupal.instantFilter.defaultSettings = {
+  itemSelector      : '.instantfilter-item',
+  ignoreSelector    : '.instantfilter-ignore',
+  groupSelector     : '.instantfilter-group',
+  updateItemZebra   : true,
+  updateGroupZebra  : false,
+
+  label             : Drupal.t('Filter'),
+  description       : Drupal.t('Enter text to filter the table below.'),
+  empty             : Drupal.t('There were no results.')
+};
+
+/**
+ * Creates an input textfield inside the container.
+ */
+Drupal.instantFilter.prototype.createTextfield = function () {
+  var self = this;
+
+  this.container.prepend(Drupal.theme('textfield', this.settings));
+  this.textfield = $('#edit-' + this.settings.name, this.container).bind('click.drupal-instantfilter keyup.drupal-instantfilter', function () {
+    self.filter($.trim($(this).val().toLowerCase()));
+  });
+
+  if (this.textfield[0]) {
+    this.textfield[0].type = 'search';
+    this.textfield.attr('results', 5);
+  };
+};
+
+/**
+ * Adds a DOM element after the textfield element and displays a 'no results' message.
+ */
+Drupal.instantFilter.prototype.addPlaceholder = function () {
+  if (!this.container.find('.instantfilter-no-results').length) {
+    this.textfield.closest('.form-item').after($('<p class="instantfilter-no-results"></p>').text(this.settings.empty));
+  };
+};
+
+/**
+ * Removes the 'no results' message.
+ */
+Drupal.instantFilter.prototype.removePlaceholder = function () {
+  this.container.find('.instantfilter-no-results').remove();
+};
+
+/**
+ * Get text value of an item.
+ */
+Drupal.instantFilter.prototype.getText = function (element) {
+  var text = '';
+  if (!$(element).is(this.settings.ignoreSelector)) {
+    for (var i = 0; i < element.childNodes.length; i++) {
+      if (element.childNodes[i].nodeType == 1) {
+        text += this.getText(element.childNodes[i]);
+      }
+      else if (element.childNodes[i].nodeType != 8) {
+        text += element.childNodes[i].nodeValue;
+      }
+    }
+  }
+  return text.toLowerCase();
+};
+
+/**
+ * TODO comments.
+ */
+Drupal.instantFilter.prototype.rebuildIndex = function () {
+  var self = this;
+
+  this.items = $.map(this.container.find(this.settings.itemSelector), function (element) {
+    var item = {
+      element: element,
+      text: self.getText(element),
+      groups: {}
+    };
+
+    return $.data(element, 'instantfilter', item);
+  });
+
+  this.groups = $.map(this.container.find(this.settings.groupSelector), function (element, groupIndex) {
+    var group = {
+      element: element,
+      total: 0,
+      results: 0
+    };
+
+    // Link group to items.
+    $(element).find(self.settings.itemSelector).each(function () {
+      $.data(this, 'instantfilter').groups[groupIndex] = group;
+
+      group.total++;
+    });
+
+    return $.data(this, 'instantfilter', group);
+  });
+};
+
+/**
+ * Filters all instant searchable items for the given string.
+ *
+ * All items containing the string will stay visible, while other items are
+ * hidden. All groups that don't have any matching items will also be hidden.
+ */
+Drupal.instantFilter.prototype.filter = function (search) {
+  var self = this;
+
+  if (this.items === undefined) {
+    this.rebuildIndex();
+  }
+
+  this.search = search;
+
+  // Reset the total and per-group result counters.
+  this.results = 0;
+  $.each(this.groups, function () {
+    this.results = 0;
+  });
+
+  // Allow other scripts to interact with this event.
+  this.container.trigger('DrupalBeforeInstantFilter', this);
+
+  var lastParent;
+  // Walk through items and hide items that don't match.
+  $.each(this.items, function (i, item) {
+    var match = this.text.indexOf(self.search) >= 0;
+
+    if (match) {
+      // Increment the total results counter.
+      self.results++;
+      // Increment the per-group result counter of item's groups.
+      $.each(this.groups, function () {
+        this.results++;
+      });
+    }
+
+    $(this.element)
+      [match ? 'addClass' : 'removeClass']('instantfilter-match')
+      [match ? 'removeClass' : 'addClass']('instantfilter-irrelevant');
+
+    if (self.settings.updateItemZebra) {
+      var currentParent = $.data(this.element.parentNode);
+      if (currentParent != lastParent && self.items[i-1]) {
+        var $item = $(self.items[i-1].element);
+
+        // Only update zebra if item actually has an odd or even class.
+        if ($item.hasClass('odd') || $item.hasClass('even')) {
+          $item.parent().children(':visible')
+            .removeClass('odd even')
+            .filter(':odd').addClass('even').end()
+            .filter(':even').addClass('odd');
+        }
+      }
+      lastParent = currentParent;
+    }
+  });
+
+  // Hide groups that have no matching items.
+  $.each(this.groups, function () {
+    $(this.element)
+      [this.results ? 'addClass' : 'removeClass']('instantfilter-has-contents')
+      [this.results ? 'removeClass' : 'addClass']('instantfilter-no-contents');
+  });
+
+  // If any results are found, remove the 'no results' message.
+  // Otherwise dipslay the 'no results message.
+  if (this.results) {
+    this.removePlaceholder();
+  }
+  else {
+    this.addPlaceholder();
+  };
+
+  // Allow other scripts to interact with this event.
+  this.container.trigger('DrupalInstantFilter', this);
+};
+
+/**
+ * Theme an input textfield
+ */
+Drupal.theme.prototype.textfield = function (element) {
+  var output = '';
+  if (element.label) {
+    output += '<label for="' + (element.id || 'edit-' + element.name) + '">' + element.label + '</label>';
+  };
+  output += '<input type="text" maxlength="' + (element.maxlength || 128) + '" name="' + (element.name) + '" id="' + (element.id || 'edit-' + element.name) + '" size="' + (element.size || 30) + '" value="' + (element.value || '') + '" class="form-text' + (element['class'] ? ' ' + element['class'] : '') + '" />';
+
+  if (element.description) {
+    output += '<div class="description">' + element.description + '</div>';
+  };
+
+  return '<div class="form-item form-type-textfield">' + output + '</div>';
+};
+
+})(jQuery);
Index: misc/instantfilter.css
===================================================================
RCS file: misc/instantfilter.css
diff -N misc/instantfilter.css
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ misc/instantfilter.css	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,10 @@
+/* $Id$ */
+
+.instantfilter-irrelevant,
+.instantfilter-no-contents {
+  display: none !important;
+}
+
+.instantfilter-no-results {
+  padding: 0 0 16px 0;
+}
