Index: lucene/highlighter/src/test/org/apache/lucene/search/postingshighlight/TestPostingsHighlighterRanking.java
===================================================================
--- lucene/highlighter/src/test/org/apache/lucene/search/postingshighlight/TestPostingsHighlighterRanking.java	(revision 1465285)
+++ lucene/highlighter/src/test/org/apache/lucene/search/postingshighlight/TestPostingsHighlighterRanking.java	(working copy)
@@ -324,4 +324,39 @@
     ir.close();
     dir.close();
   }
+  
+  public void testSimpleProximity() throws Exception {
+    Directory dir = newDirectory();
+    IndexWriterConfig iwc = newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random(), MockTokenizer.SIMPLE, true));
+    iwc.setMergePolicy(newLogMergePolicy());
+    RandomIndexWriter iw = new RandomIndexWriter(random(), dir, iwc);
+    
+    FieldType offsetsType = new FieldType(TextField.TYPE_STORED);
+    offsetsType.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS);
+    Field body = new Field("body", "", offsetsType);
+    Document doc = new Document();
+    doc.add(body);
+    
+    body.setStringValue("Foo is present in this sentence, but quite some distance away from bar. " + 
+                        "On the other hand this sentence contains a string exactly of foo bar. " + 
+                        "This has only bar bar bar bar bar bar bar bar bar bar bar bar.");
+    iw.addDocument(doc);
+    
+    IndexReader ir = iw.getReader();
+    iw.close();
+    
+    IndexSearcher searcher = newSearcher(ir);
+    PostingsHighlighter highlighter = new PostingsHighlighter();
+    BooleanQuery query = new BooleanQuery();
+    query.add(new TermQuery(new Term("body", "foo")), BooleanClause.Occur.SHOULD);
+    query.add(new TermQuery(new Term("body", "bar")), BooleanClause.Occur.SHOULD);
+    TopDocs topDocs = searcher.search(query, null, 10, Sort.INDEXORDER);
+    assertEquals(1, topDocs.totalHits);
+    String snippets[] = highlighter.highlight("body", query, searcher, topDocs, 1);
+    assertEquals(1, snippets.length);
+    assertTrue(snippets[0], snippets[0].startsWith("On the other hand"));
+    
+    ir.close();
+    dir.close();
+  }
 }
Index: lucene/highlighter/src/java/org/apache/lucene/search/postingshighlight/PassageScorer.java
===================================================================
--- lucene/highlighter/src/java/org/apache/lucene/search/postingshighlight/PassageScorer.java	(revision 1465285)
+++ lucene/highlighter/src/java/org/apache/lucene/search/postingshighlight/PassageScorer.java	(working copy)
@@ -84,10 +84,19 @@
    * @param passageLen length of the passage in characters.
    * @return term weight
    */
-  public float tf(int freq, int passageLen) {
+  public float tf(float freq, int passageLen) {
     float norm = k1 * ((1 - b) + b * (passageLen / pivot));
     return freq / (freq + norm);
   }
+  
+  /**
+   * Increments accumulator for proximity match 
+   * @param distance number of positions between this term and an adjacent term
+   * @return increment
+   */
+  public float inc(int distance) {
+    return 1f/(distance * distance);
+  }
     
   /**
    * Normalize a passage according to its position in the document.
Index: lucene/highlighter/src/java/org/apache/lucene/search/postingshighlight/PostingsHighlighter.java
===================================================================
--- lucene/highlighter/src/java/org/apache/lucene/search/postingshighlight/PostingsHighlighter.java	(revision 1465285)
+++ lucene/highlighter/src/java/org/apache/lucene/search/postingshighlight/PostingsHighlighter.java	(working copy)
@@ -474,12 +474,12 @@
 
       if (doc == pDoc) {
         weights[i] = scorer.weight(contentLength, de.freq());
-        de.nextPosition();
-        pq.add(new OffsetsEnum(de, i));
+        int pos = de.nextPosition();
+        pq.add(new OffsetsEnum(de, i, pos));
       }
     }
     
-    pq.add(new OffsetsEnum(EMPTY, Integer.MAX_VALUE)); // a sentinel for termination
+    pq.add(new OffsetsEnum(EMPTY, Integer.MAX_VALUE, Integer.MAX_VALUE)); // a sentinel for termination
     
     PriorityQueue<Passage> passageQueue = new PriorityQueue<Passage>(n, new Comparator<Passage>() {
       @Override
@@ -495,6 +495,11 @@
     });
     Passage current = new Passage();
     
+    int freqs[] = new int[terms.length]; // per-term frequencies
+    float accums[] = new float[terms.length]; // per-term proximity accumulators
+    int lastTermID = -1;
+    int lastPosition = 0;
+    
     OffsetsEnum off;
     while ((off = pq.poll()) != null) {
       final DocsAndPositionsEnum dp = off.dp;
@@ -506,6 +511,14 @@
       if (start >= current.endOffset) {
         if (current.startOffset >= 0) {
           // finalize current
+          for (int i = 0; i < freqs.length; i++) {
+            if (freqs[i] > 0) {
+              current.score += weights[i] * scorer.tf(freqs[i], current.endOffset - current.startOffset);
+              if (accums[i] > 0) {
+                current.score += Math.min(1, weights[i]) * scorer.tf(accums[i], current.endOffset - current.startOffset);
+              }
+            }
+          }
           current.score *= scorer.norm(current.startOffset);
           // new sentence: first add 'current' to queue 
           if (passageQueue.size() == n && current.score < passageQueue.peek().score) {
@@ -519,6 +532,9 @@
               current = new Passage();
             }
           }
+          Arrays.fill(freqs, 0);
+          Arrays.fill(accums, 0f);
+          lastTermID = -1;
         }
         // if we exceed limit, we are done
         if (start >= contentLength) {
@@ -541,24 +557,24 @@
         current.startOffset = Math.max(bi.preceding(start+1), 0);
         current.endOffset = Math.min(bi.next(), contentLength);
       }
-      int tf = 0;
-      while (true) {
-        tf++;
-        current.addMatch(start, end, terms[off.id]);
-        if (off.pos == dp.freq()) {
-          break; // removed from pq
-        } else {
-          off.pos++;
-          dp.nextPosition();
-          start = dp.startOffset();
-          end = dp.endOffset();
+      freqs[off.id]++;
+      current.addMatch(start, end, terms[off.id]);
+      if (lastTermID >= 0) {
+        int distance = off.pos - lastPosition;
+        assert distance >= 0;
+        if (lastTermID != off.id && distance > 0) {
+          float incr = scorer.inc(distance);
+          accums[lastTermID] += weights[off.id] * incr;
+          accums[off.id] += weights[lastTermID] * incr;
         }
-        if (start >= current.endOffset) {
-          pq.offer(off);
-          break;
-        }
       }
-      current.score += weights[off.id] * scorer.tf(tf, current.endOffset - current.startOffset);
+      lastPosition = off.pos;
+      lastTermID = off.id;
+      if (off.posCount < dp.freq()) {
+        off.pos = dp.nextPosition();
+        off.posCount++;
+        pq.offer(off);
+      }
     }
 
     // Dead code but compiler disagrees:
@@ -594,12 +610,14 @@
   private static class OffsetsEnum implements Comparable<OffsetsEnum> {
     DocsAndPositionsEnum dp;
     int pos;
+    int posCount;
     int id;
     
-    OffsetsEnum(DocsAndPositionsEnum dp, int id) throws IOException {
+    OffsetsEnum(DocsAndPositionsEnum dp, int id, int pos) throws IOException {
       this.dp = dp;
       this.id = id;
-      this.pos = 1;
+      this.pos = pos;
+      this.posCount = 1;
     }
 
     @Override
