diff --git a/addon/search/search.js b/addon/search/search.js
index 4bcfbe1be1deaa08a0f629a4b542a4970dfa2902..9f804b15127290964ff8f153d4f8cddae5fc5f8b 100644
--- a/addon/search/search.js
+++ b/addon/search/search.js
@@ -57,10 +57,20 @@
     return cm.getSearchCursor(query, pos, queryCaseInsensitive(query));
   }
 
+  function persistentDialog(cm, text, deflt, f) {
+    cm.openDialog(text, f, {
+      value: deflt,
+      selectValueOnOpen: true,
+      closeOnEnter: false,
+      onClose: function() { clearSearch(cm); }
+    });
+  }
+
   function dialog(cm, text, shortText, deflt, f) {
     if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true});
     else f(prompt(shortText, deflt));
   }
+
   function confirmDialog(cm, text, shortText, fs) {
     if (cm.openConfirm) cm.openConfirm(text, fs);
     else if (confirm(shortText)) fs[0]();
@@ -80,25 +90,38 @@
   var queryDialog =
     'Search: <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>';
 
-  function doSearch(cm, rev) {
+  function startSearch(cm, state, query) {
+    state.queryText = query;
+    state.query = parseQuery(query);
+    cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
+    state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
+    cm.addOverlay(state.overlay);
+    if (cm.showMatchesOnScrollbar) {
+      if (state.annotate) { state.annotate.clear(); state.annotate = null; }
+      state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query));
+    }
+  }
+
+  function doSearch(cm, rev, persistent) {
     var state = getSearchState(cm);
     if (state.query) return findNext(cm, rev);
     var q = cm.getSelection() || state.lastQuery;
-    dialog(cm, queryDialog, "Search for:", q, function(query) {
-      cm.operation(function() {
-        if (!query || state.query) return;
-        state.query = parseQuery(query);
-        cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
-        state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
-        cm.addOverlay(state.overlay);
-        if (cm.showMatchesOnScrollbar) {
-          if (state.annotate) { state.annotate.clear(); state.annotate = null; }
-          state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query));
-        }
-        state.posFrom = state.posTo = cm.getCursor();
-        findNext(cm, rev);
+    if (persistent && cm.openDialog) {
+      persistentDialog(cm, queryDialog, q, function(query, event) {
+        CodeMirror.e_stop(event);
+        if (!query) return;
+        if (query != state.queryText) startSearch(cm, state, query);
+        findNext(cm, event.shiftKey);
       });
-    });
+    } else {
+      dialog(cm, queryDialog, "Search for:", q, function(query) {
+        if (query && !state.query) cm.operation(function() {
+          startSearch(cm, state, query);
+          state.posFrom = state.posTo = cm.getCursor();
+          findNext(cm, rev);
+        });
+      });
+    }
   }
 
   function findNext(cm, rev) {cm.operation(function() {
@@ -170,6 +193,7 @@
   }
 
   CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
+  CodeMirror.commands.findPersistent = function(cm) {clearSearch(cm); doSearch(cm, false, true);};
   CodeMirror.commands.findNext = doSearch;
   CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
   CodeMirror.commands.clearSearch = clearSearch;
diff --git a/demo/search.html b/demo/search.html
index 04ba7ac09a0995c47a82063ad80a8bcb19c89d92..21c34251e21661004bbfd592f8fee95538c093ce 100644
--- a/demo/search.html
+++ b/demo/search.html
@@ -71,18 +71,20 @@
     <script>
 var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
   mode: "text/html",
-  lineNumbers: true
+  lineNumbers: true,
+  extraKeys: {"Alt-F": "findPersistent"}
 });
 </script>
 
     <p>Demonstration of primitive search/replace functionality. The
-    keybindings (which can be overridden by custom keymaps) are:</p>
+    keybindings (which can be configured with custom keymaps) are:</p>
     <dl>
       <dt>Ctrl-F / Cmd-F</dt><dd>Start searching</dd>
       <dt>Ctrl-G / Cmd-G</dt><dd>Find next</dd>
       <dt>Shift-Ctrl-G / Shift-Cmd-G</dt><dd>Find previous</dd>
       <dt>Shift-Ctrl-F / Cmd-Option-F</dt><dd>Replace</dd>
       <dt>Shift-Ctrl-R / Shift-Cmd-Option-F</dt><dd>Replace all</dd>
+      <dt>Alt-F</dt><dd>Persistent search (dialog doesn't autoclose, enter to find next, shift-enter to find previous)</dd>
     </dl>
     <p>Searching is enabled by
     including <a href="../addon/search/search.js">addon/search/search.js</a>