Index: modules/search/search.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/search/search.module,v
retrieving revision 1.250.2.6
diff -u -r1.250.2.6 search.module
--- modules/search/search.module	10 Mar 2009 17:20:01 -0000	1.250.2.6
+++ modules/search/search.module	6 Jul 2009 17:37:24 -0000
@@ -1185,12 +1185,13 @@
   array_walk($keys, '_search_excerpt_replace');
   $workkeys = $keys;
 
-  // Extract a fragment per keyword for at most 4 keywords.
+  // Extract fragments around keywords.
   // First we collect ranges of text around each keyword, starting/ending
-  // at spaces.
+  // at spaces, trying to get to 256 characters.
   // If the sum of all fragments is too short, we look for second occurrences.
   $ranges = array();
   $included = array();
+  $foundkeys = array();
   $length = 0;
   while ($length < 256 && count($workkeys)) {
     foreach ($workkeys as $k => $key) {
@@ -1207,10 +1208,30 @@
       if (!isset($included[$key])) {
         $included[$key] = 0;
       }
-      // Locate a keyword (position $p), then locate a space in front (position
-      // $q) and behind it (position $s)
-      if (preg_match('/'. $boundary . $key . $boundary .'/iu', $text, $match, PREG_OFFSET_CAPTURE, $included[$key])) {
+      // Locate a keyword (position $p, always >0 because $text starts with
+      // a space). First try bare keyword, but if that doesn't work, let
+      // stemming modules try to find a derived form.
+      $p = 0;
+      if (preg_match('/'. $boundary . $key . $boundary . '/iu', $text, $match, PREG_OFFSET_CAPTURE, $included[$key])) {
         $p = $match[0][1];
+      }
+      else {
+        foreach (module_implements('search_excerpt_match') as $module) {
+          $info = module_invoke($module, 'search_excerpt_match',
+            $key, $text, $included[$key], $boundary);
+          if ($info['where']) {
+            $p = $info['where'];
+            if ($info['keyword']) {
+              $foundkeys[] = $info['keyword'];
+            }
+            break;
+          }
+        }
+      }
+      // Now locate a space in front (position $q) and behind it (position $s),
+      // leaving about 60 characters extra before and after for context.
+      // Note that a space was added to the front and end of $text above.
+      if ($p) {
         if (($q = strpos($text, ' ', max(0, $p - 60))) !== FALSE) {
           $end = substr($text, $p, 80);
           if (($s = strrpos($end, ' ')) !== FALSE) {
@@ -1267,6 +1288,7 @@
   $text = (isset($newranges[0]) ? '' : '... ') . implode(' ... ', $out) .' ...';
 
   // Highlight keywords. Must be done at once to prevent conflicts ('strong' and '<strong>').
+  $keys = $keys + $foundkeys;
   $text = preg_replace('/'. $boundary .'('. implode('|', $keys) .')'. $boundary .'/iu', '<strong>\0</strong>', $text);
   return $text;
 }
