diff --git a/src/proto.h b/src/proto.h
index aabca6ab8efda5c6b03fc2386813d7cccb388e47..605f2cde12302d397a3ad69a3df6598f90a805df 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -681,7 +681,10 @@ void total_refresh(void);
 void display_main_list(void);
 void do_cursorpos(bool force);
 void do_cursorpos_void(void);
-void spotlight(bool active, const char *word);
+void spotlight(bool active, size_t from_col, size_t to_col);
+#ifndef NANO_TINY
+void spotlight_softwrapped(bool active, size_t from_col, size_t to_col);
+#endif
 void xon_complaint(void);
 void xoff_complaint(void);
 void do_suspend_void(void);
diff --git a/src/search.c b/src/search.c
index 6d178200aa491b8509469b15f905d8d6b536e926..2c36a5b5bf0c378a82c5daefa6424a37d52ba5c1 100644
--- a/src/search.c
+++ b/src/search.c
@@ -614,10 +614,9 @@ ssize_t do_replace_loop(const char *needle, bool whole_word_only,
 	    numreplaced = 0;
 
 	if (!replaceall) {
-	    size_t xpt = xplustabs();
-	    char *exp_word = display_string(openfile->current->data,
-			xpt, strnlenpt(openfile->current->data,
-			openfile->current_x + match_len) - xpt, FALSE);
+	    size_t from_col = xplustabs();
+	    size_t to_col = strnlenpt(openfile->current->data,
+					openfile->current_x + match_len);
 
 	    /* Refresh the edit window, scrolling it if necessary. */
 	    edit_refresh();
@@ -625,14 +624,12 @@ ssize_t do_replace_loop(const char *needle, bool whole_word_only,
 	    /* Don't show cursor, to not distract from highlighted match. */
 	    curs_set(0);
 
-	    spotlight(TRUE, exp_word);
+	    spotlight(TRUE, from_col, to_col);
 
 	    /* TRANSLATORS: This is a prompt. */
 	    i = do_yesno_prompt(TRUE, _("Replace this instance?"));
 
-	    spotlight(FALSE, exp_word);
-
-	    free(exp_word);
+	    spotlight(FALSE, from_col, to_col);
 
 	    if (i == -1)  /* The replacing was cancelled. */
 		break;
diff --git a/src/text.c b/src/text.c
index 3bb76c082ec3ebf6c017d1cfe9a7bc1e71c5f65d..4a8d38cf1962d1ad3fb391b5b117cd50c03fb062 100644
--- a/src/text.c
+++ b/src/text.c
@@ -2540,7 +2540,7 @@ void do_full_justify(void)
  * return FALSE if the user cancels. */
 bool do_int_spell_fix(const char *word)
 {
-    char *save_search, *exp_word;
+    char *save_search;
     size_t firstcolumn_save = openfile->firstcolumn;
     size_t current_x_save = openfile->current_x;
     filestruct *edittop_save = openfile->edittop;
@@ -2605,11 +2605,12 @@ bool do_int_spell_fix(const char *word)
 	proceed = TRUE;
 	napms(2800);
     } else if (result == 1) {
-	exp_word = display_string(openfile->current->data, xplustabs(),
-					strlenpt(word), FALSE);
+	size_t from_col = xplustabs();
+	size_t to_col = from_col + strlenpt(word);
+
 	edit_refresh();
 
-	spotlight(TRUE, exp_word);
+	spotlight(TRUE, from_col, to_col);
 
 	/* Let the user supply a correctly spelled alternative. */
 	proceed = (do_prompt(FALSE, FALSE, MSPELL, word,
@@ -2618,9 +2619,7 @@ bool do_int_spell_fix(const char *word)
 #endif
 				edit_refresh, _("Edit a replacement")) != -1);
 
-	spotlight(FALSE, exp_word);
-
-	free(exp_word);
+	spotlight(FALSE, from_col, to_col);
 
 	/* If a replacement was given, go through all occurrences. */
 	if (proceed && strcmp(word, answer) != 0) {
diff --git a/src/winio.c b/src/winio.c
index c8e8cc77e52b1b30d852f7f48ff7be3230b4c2c7..8c1edcfd93c894eb216917651667491c234498e0 100644
--- a/src/winio.c
+++ b/src/winio.c
@@ -3396,32 +3396,43 @@ void enable_waiting(void)
     nodelay(edit, FALSE);
 }
 
-/* Highlight the current word being replaced or spell checked.  We
- * expect word to have tabs and control characters expanded. */
-void spotlight(bool active, const char *word)
+/* Highlight the text between from_col and to_col when active is TRUE.
+ * Remove the highlight when active is FALSE. */
+void spotlight(bool active, size_t from_col, size_t to_col)
 {
-    size_t word_span = strlenpt(word);
-    size_t room = word_span;
+    char *word;
+    size_t word_span, room;
 
-    /* Compute the number of columns that are available for the word. */
-    if (!ISSET(SOFTWRAP)) {
-	room = editwincols + get_page_start(xplustabs()) - xplustabs();
-
-	/* If the word is partially offscreen, reserve space for the "$". */
-	if (word_span > room)
-	    room--;
+#ifndef NANO_TINY
+    if (ISSET(SOFTWRAP)) {
+	spotlight_softwrapped(active, from_col, to_col);
+	return;
     }
+#endif
 
     place_the_cursor(FALSE);
 
+    /* This is so we can show zero-length matches. */
+    if (to_col == from_col) {
+	word = mallocstrcpy(NULL, " ");
+	to_col++;
+    } else
+	word = display_string(openfile->current->data, from_col,
+				to_col - from_col, FALSE);
+
+    word_span = strlenpt(word);
+
+    /* Compute the number of columns that are available for the word. */
+    room = editwincols + get_page_start(from_col) - from_col;
+
+    /* If the word is partially offscreen, reserve space for the "$". */
+    if (word_span > room)
+	room--;
+
     if (active)
 	wattron(edit, hilite_attribute);
 
-    /* This is so we can show zero-length matches. */
-    if (word_span == 0)
-	waddch(edit, ' ');
-    else
-	waddnstr(edit, word, actual_x(word, room));
+    waddnstr(edit, word, actual_x(word, room));
 
     if (word_span > room)
 	waddch(edit, '$');
@@ -3429,8 +3440,70 @@ void spotlight(bool active, const char *word)
     if (active)
 	wattroff(edit, hilite_attribute);
 
+    free(word);
+
+    wnoutrefresh(edit);
+}
+
+#ifndef NANO_TINY
+/* Highlight the text between from_col and to_col when active is TRUE; remove
+ * the highlight when active is FALSE.  This will not highlight softwrapped
+ * line breaks, since they're not actually part of the spotlighted text. */
+void spotlight_softwrapped(bool active, size_t from_col, size_t to_col)
+{
+    ssize_t row;
+    size_t leftedge = get_chunk_leftedge(openfile->current, from_col);
+    size_t break_col;
+    bool end_of_line;
+    char *word;
+
+    place_the_cursor(FALSE);
+
+    row = openfile->current_y;
+
+    while (row < editwinrows) {
+	break_col = get_softwrap_breakpoint(openfile->current->data,
+						leftedge, &end_of_line);
+
+	/* Stop after the end of the word, by pretending the end of the word is
+	 * the end of the line. */
+	if (break_col >= to_col) {
+	    end_of_line = TRUE;
+	    break_col = to_col;
+	}
+
+	/* This is so we can show zero-length matches. */
+	if (break_col == from_col) {
+	    word = mallocstrcpy(NULL, " ");
+	    break_col++;
+	} else
+	    word = display_string(openfile->current->data, from_col,
+					break_col - from_col, FALSE);
+
+	if (active)
+	    wattron(edit, hilite_attribute);
+
+	waddnstr(edit, word, actual_x(word, break_col));
+
+	if (active)
+	    wattroff(edit, hilite_attribute);
+
+	free(word);
+
+	if (end_of_line)
+	    break;
+
+	row++;
+
+	wmove(edit, row, 0);
+
+	leftedge = break_col;
+	from_col = break_col;
+    }
+
     wnoutrefresh(edit);
 }
+#endif
 
 #ifndef DISABLE_EXTRA
 #define CREDIT_LEN 54