Index: /CKEditor/branches/prototype/_source/core/dom/domwalker.js
===================================================================
--- /CKEditor/branches/prototype/_source/core/dom/domwalker.js	(revision 2902)
+++ /CKEditor/branches/prototype/_source/core/dom/domwalker.js	(revision 2903)
@@ -29,5 +29,5 @@
 		};
 
-	CKEDITOR.domWalker = function( node )
+	CKEDITOR.dom.domWalker = function( node )
 	{
 		if ( arguments.length < 1 )
@@ -38,5 +38,5 @@
 	};
 
-	CKEDITOR.domWalker.prototype = {
+	CKEDITOR.dom.domWalker.prototype = {
 		next : (function()
 		{
@@ -196,5 +196,5 @@
 	};
 
-	CKEDITOR.domWalker.blockBoundary = function( customNodeNames )
+	CKEDITOR.dom.domWalker.blockBoundary = function( customNodeNames )
 	{
 		return function( evt )
@@ -232,7 +232,7 @@
 	};
 
-	CKEDITOR.domWalker.listItemBoundary = function()
+	CKEDITOR.dom.domWalker.listItemBoundary = function()
 	{
-		return CKEDITOR.domWalker.blockBoundary( { br : 1 } );
+		return CKEDITOR.dom.domWalker.blockBoundary( { br : 1 } );
 	};
 })();
Index: /CKEditor/branches/prototype/_source/core/dom/range.js
===================================================================
--- /CKEditor/branches/prototype/_source/core/dom/range.js	(revision 2902)
+++ /CKEditor/branches/prototype/_source/core/dom/range.js	(revision 2903)
@@ -989,7 +989,7 @@
 						endNode = boundaryNodes.endNode,
 						guardFunction = ( unit == CKEDITOR.ENLARGE_BLOCK_CONTENTS ? 
-							CKEDITOR.domWalker.blockBoundary() :
-							CKEDITOR.domWalker.listItemBoundary() ),
-						walker = new CKEDITOR.domWalker( startNode ),
+							CKEDITOR.dom.domWalker.blockBoundary() :
+							CKEDITOR.dom.domWalker.listItemBoundary() ),
+						walker = new CKEDITOR.dom.domWalker( startNode ),
 						data = walker.reverse( guardFunction ),
 						boundaryEvent = data.events.shift();
@@ -1277,9 +1277,9 @@
 
 			var startNode = getBoundaryNodes.apply( this ).startNode,
-				walker = new CKEDITOR.domWalker( startNode );
+				walker = new CKEDITOR.dom.domWalker( startNode );
 
 			// DFS backwards until the block boundary, with the checker function.
 			walker.on( 'step', getCheckStartEndBlockFunction( true ), null, null, 20 );
-			walker.reverse( CKEDITOR.domWalker.blockBoundary() );
+			walker.reverse( CKEDITOR.dom.domWalker.blockBoundary() );
 
 			return !walker.checkFailed;
@@ -1301,9 +1301,9 @@
 
 			var endNode = getBoundaryNodes.apply( this ).endNode,
-				walker = new CKEDITOR.domWalker( endNode );
+				walker = new CKEDITOR.dom.domWalker( endNode );
 
 			// DFS forward until the block boundary, with the checker function.
 			walker.on( 'step', getCheckStartEndBlockFunction( false ), null, null, 20 );
-			walker.forward( CKEDITOR.domWalker.blockBoundary() );
+			walker.forward( CKEDITOR.dom.domWalker.blockBoundary() );
 
 			return !walker.checkFailed;
Index: /CKEditor/branches/prototype/_source/plugins/find/dialogs/find.js
===================================================================
--- /CKEditor/branches/prototype/_source/plugins/find/dialogs/find.js	(revision 2902)
+++ /CKEditor/branches/prototype/_source/plugins/find/dialogs/find.js	(revision 2903)
@@ -26,4 +26,5 @@
 		if ( evt.to && evt.to.type == CKEDITOR.NODE_TEXT && evt.to.$.length > 0 )
 			this.stop();
+		CKEDITOR.dom.domWalker.blockBoundary( { br : 1 } ).call( this, evt );
 	};
 
@@ -39,5 +40,9 @@
 	var fireCharacterEvent = function()
 	{
-		var obj = { type : 'character', character : this._.textNode ? this._.textNode.getText().charAt( this._.offset ) : null };
+		var obj = {
+			type : 'character',
+			character : this.textNode ? this.textNode.getText().charAt( this.offset ) : null,
+			hitMatchBoundary : this._.matchBoundary
+		};
 		this.fire( 'character', obj );
 		this._.actionEvents.push( obj );
@@ -49,15 +54,12 @@
 		var characterWalker = function( cursorOrTextNode, offset )
 		{
-			if ( arguments.length < 2 )
-				return;
-
 			var isCursor = ( cursorOrTextNode instanceof characterWalker );
-			CKEDITOR.tools.extend( this._ || ( this._ = {} ),
-				{
-					textNode : isCursor ? cursorOrTextNode._.textNode : cursorOrTextNode,
-					offset : isCursor ? cursorOrTextNode._.offset : offset,
-					actionEvents : null
-				} );
-			this._.walker = new CKEDITOR.dom.domWalker( this._.textNode );
+			this._ = {
+				matchBoundary : false,
+				actionEvents : null
+			};
+			this.textNode = isCursor ? cursorOrTextNode.textNode : cursorOrTextNode,
+			this.offset = isCursor ? cursorOrTextNode.offset : offset,
+			this._.walker = new CKEDITOR.dom.domWalker( this.textNode );
 			CKEDITOR.event.implementOn( this );
 			domWalkerEventBridge.call( this );
@@ -67,18 +69,27 @@
 			next : function()
 			{
-				// Clear any previous action events.
+				// Clear any previous action events and related flags.
 				this._.actionEvents = [];
+				this._.matchBoundary = false;
 
 				// If there are more characters in the text node, get it and raise an event.
-				if ( this._.offset < this._.textNode.getLength() - 1 )
-				{
-					this._.offset++;
+				if ( this.textNode.type == CKEDITOR.NODE_TEXT && this.offset < this.textNode.getLength() - 1 )
+				{
+					this.offset++;
 					return fireCharacterEvent.call( this );
 				}
 
 				// If we are at the end of the text node, use dom walker to get the next text node.
-				var data = this._.walker.forward( guardDomWalkerNonEmptyTextNode );
-				this._.textNode = data.node;
-				this._.offset = 0;
+				var data = null;
+				while ( !data || ( data.node && data.node.type != CKEDITOR.NODE_TEXT ) )
+				{
+					data = this._.walker.forward( guardDomWalkerNonEmptyTextNode );
+
+					// Block boundary? BR? Document boundary?
+					if ( !data.node || data.node.type != CKEDITOR.NODE_TEXT )
+						this._.matchBoundary = true;
+				}
+				this.textNode = data.node;
+				this.offset = 0;
 				return fireCharacterEvent.call( this );
 			},
@@ -86,27 +97,247 @@
 			back : function()
 			{
-				// Clear any previous action events.
+				// Clear any previous action events and related flags.
 				this._.actionEvents = [];
+				this._.matchBoundary = false;
 
 				// More characters -> decrement offset and return.
-				if ( this._.offset > 0 )
-				{
-					this._.offset--;
+				if ( this.textNode.type == CKEDITOR.NODE_TEXT && this.offset > 0 )
+				{
+					this.offset--;
 					return fireCharacterEvent.call( this );
 				}
 
 				// Start of text node -> use dom walker to get the previous text node.
-				var data = this._.walker.reverse( guardDomWalkerNonEmptyTextNode );
-				this._.textNode = data.node;
-				this._.offset = data.node.length - 1;
+				var data = null;
+				while ( !data || ( data.node && data.node.type != CKEDITOR.NODE_TEXT ) )
+				{
+					data = this._.walker.reverse( guardDomWalkerNonEmptyTextNode );
+
+					// Block boundary? BR? Document boundary?
+					if ( !data.node || data.node.type != CKEDITOR.NODE_TEXT )
+						this._.matchBoundary = true;
+				}
+				this.textNode = data.node;
+				this.offset = data.node.length - 1;
 				return fireCharacterEvent.call( this );
+			},
+
+			getChar : function()
+			{
+				return this.textNode ? this.textNode.getText().charAt( this.offset ) : null;
 			}
 		};
 
-		var characterRange = function()
+		var characterRange = function( initCursor, maxLength )
 		{
+			this._ = {
+				cursors : initCursor.push ? initCursor : [ initCursor ],
+				maxLength : maxLength,
+		   		highlightRange : null
+			};
 		};
 
 		characterRange.prototype = {
+			toDomRange : function()
+			{
+				var cursors = this._.cursors;
+				if ( cursors.length < 1 )
+					return null;
+
+				var first = cursors[0],
+					last = cursors[ cursors.length - 1 ],
+					range = new CKEDITOR.dom.range( editor.document );
+
+				range.setStart( first.textNode, first.offset );
+				range.setEnd( last.textNode, last.offset + 1 );
+				return range;
+			},
+
+			highlight : function()
+			{
+				// TODO: wait till Fred has implemented style removal for this.
+				editor.getSelection().selectRanges( [ this.toDomRange() ] );
+			},
+
+			removeHighlight : function()
+			{
+				// TODO: wait till Fred has implemented style removal for this.
+			},
+
+			getHighlightDomRange : function()
+			{
+				// TODO: wait till Fred has implemented style removal for this.
+				return this.toDomRange();
+			},
+
+			moveNext : function()
+			{
+				var next = new characterWalker( this._.cursors[ this._.cursors.length - 1 ] ),
+					retval = next.next(),
+					cursors = this._.cursors;
+
+				// Clear the cursors queue if we've crossed a match boundary.
+				if ( retval.hitMatchBoundary )
+					this._.cursors = cursors = [];
+
+				cursors.push( next );
+				if ( cursors.length > this._.maxLength )
+					cursors.shift();
+
+				return retval;
+			},
+			
+			moveBack : function()
+			{
+				var prev = new characterWalker( this._.cursors[0] ),
+					retval = prev.back(),
+					cursors = this._.cursors;
+
+				if ( retval.hitMatchBoundary )
+					this._.cursors = cursors = [];
+
+				cursors.unshift( prev );
+				if ( cursors.length > this._.maxLength )
+					cursors.pop();
+
+				return retval;
+			},
+
+			getEndCharacter : function()
+			{
+				var cursors = this._.cursors;
+				if ( cursors.length < 1 )
+					return null;
+
+				return cursors[ cursors.length - 1 ].getChar();
+			},
+
+			getNextRange : function( maxLength )
+			{
+				var cursors = this._.cursors;
+				if ( cursors.length < 1 )
+					return null;
+
+				var next = new characterWalker( cursors[ cursors.length - 1 ] );
+				next.next();
+				return new characterRange( next, maxLength );
+			},
+
+			getCursors : function()
+			{
+				return this._.cursors;
+			}
+		};
+
+		var KMP_NOMATCH = 0,
+			KMP_ADVANCED = 1,
+			KMP_MATCHED = 2;
+		var kmpMatcher = function( pattern, ignoreCase )
+		{
+			var overlap = [ -1 ];
+			for ( var i = 0 ; i < pattern.length ; i++ )
+			{
+				overlap.push( overlap[i] + 1 );
+				while ( overlap[ i + 1 ] > 0 && pattern.charAt( i ) != pattern.charAt( overlap[ i + 1 ] - 1 ) )
+					overlap[ i + 1 ] = overlap[ overlap[ i + 1 ] - 1 ] + 1;
+			}
+
+			this._ = {
+				overlap : overlap,
+				state : 0,
+				ignoreCase : !!ignoreCase,
+				pattern : ignoreCase ? pattern.toLowerCase() : pattern
+			};
+		};
+		kmpMatcher.prototype = {
+			feedCharacter : function( c )
+			{
+				if ( this._.ignoreCase )
+					c = c.toLowerCase();
+
+				while ( true )
+				{
+					if ( c == this._.pattern.charAt( this._.state ) )
+					{
+						this._.state++;
+						if ( this._.state == this._.pattern.length )
+						{
+							this._.state = 0;
+							return KMP_MATCHED;
+						}
+						return KMP_ADVANCED;
+					}
+					else if ( this._.state == 0 )
+						return KMP_NOMATCH;
+					else
+						this._.state = this._.overlap[ this._.state ];
+				}
+
+				return null;
+			},
+
+			reset : function()
+			{
+				this._.state = 0;
+			}
+		};
+
+		var wordSeparatorRegex = /[.,"'?!;: \u0085\u00a0\u1680\u280e\u2028\u2029\u202f\u205f\u3000]/,
+			isWordSeparator = function( c )
+			{
+				if ( !c )
+					return true;
+				var code = c.charCodeAt( 0 );
+				return ( code >= 9 && code <= 0xd ) || ( code >= 0x2000 && code <= 0x200a ) || wordSeparatorRegex.test( c );
+			};
+
+		var finder = {
+			range : null,
+			find : function( pattern, matchCase, matchWord )
+			{
+				if ( !this.range )
+					this.range = new characterRange( new characterWalker( editor.document.getBody(), 0 ), pattern.length );
+				else
+				{
+					this.range.removeHighlight();
+					this.range = this.range.getNextRange( pattern.length );
+				}
+
+				var matcher = new kmpMatcher( pattern, !matchCase ),
+					matchState = KMP_NOMATCH,
+					character = '%';
+
+				while ( character != null )
+				{
+					while ( ( character = this.range.getEndCharacter() ) )
+					{
+						matchState = matcher.feedCharacter( character );
+						if ( matchState == KMP_MATCHED )
+							break;
+						if ( this.range.moveNext() )
+							matcher.reset();
+					}
+
+					if ( matchState == KMP_MATCHED )
+					{
+						if ( matchWord )
+						{
+							var cursors = this.range.getCursors(),
+								head = new characterCursor( cursors[ cursors.length - 1 ] ),
+								tail = new characterCursor( cursors[0] );
+							if ( !( head.next().character || isWordSeparator( head.getChar() ) ) )
+								continue;
+							if ( !( tail.back().character || isWordSeparator( tail.getChar() ) ) )
+								continue;
+						}
+
+						this.range.highlight();
+						return true;
+					}
+				}
+
+				this.range = null;
+				return false;
+			}
 		};
 
@@ -144,5 +375,9 @@
 									onClick : function()
 									{
-										alert( editor.lang.findAndReplace.notFoundMsg );
+										var dialog = this.getDialog();
+										if ( !finder.find( dialog.getValueOf( 'find', 'txtFindFind' ),
+													dialog.getValueOf( 'find', 'txtFindCaseChk' ),
+													dialog.getValueOf( 'find', 'txtFindWordChk' ) ) )
+											alert( editor.lang.findAndReplace.notFoundMsg );
 									}
 								},
@@ -255,4 +490,12 @@
 				else
 					this.getContentElement( 'find', 'txtFindFind' ).focus();
+			},
+			onHide : function()
+			{
+				if ( finder.range )
+				{
+					finder.range.removeHighlight();
+					editor.getSelection().selectRanges( [finder.range.toDomRange()] );
+				}
 			}
 		};
