Fix some bugs in editing feature

- Fixed caching of words - some StringBuilders were being recycled while also being cached. Making copies now.
- Fixed regression in revert - don't reset the word composer after saving the accepted word.
- Removed flicker when cursoring through a word - delay the abortCorrection() until we need to and do the correction
   as an atomic operation.
- Fixed replacing of "selected" words (double-tap to select a word)

Still to do:
- Remove flicker on highlighting a word - may need a framework change
- Don't remove spans on text that's already in the text field - may require a framework change.
- Figure out what to do about the punctuations that share the suggestion strip when in correction mode.
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index c19428d..b1b6d92 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -49,8 +49,8 @@
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewParent;
 import android.view.ViewGroup;
+import android.view.ViewParent;
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.inputmethod.CompletionInfo;
@@ -693,12 +693,14 @@
 
 
         // If a word is selected
-        if ((candidatesStart == candidatesEnd || newSelStart != oldSelStart)
+        if (isPredictionOn() && mJustRevertedSeparator == null
+                && (candidatesStart == candidatesEnd || newSelStart != oldSelStart)
                 && (newSelStart < newSelEnd - 1 || (!mPredicting))
                 && !mVoiceInputHighlighted) {
-            abortCorrection(false);
             if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
                 postUpdateOldSuggestions();
+            } else {
+                abortCorrection(false);
             }
         }
     }
@@ -1083,6 +1085,8 @@
         InputConnection ic = getCurrentInputConnection();
         if (ic == null) return;
 
+        ic.beginBatchEdit();
+
         if (mAfterVoiceInput) {
             // Don't log delete if the user is pressing delete at
             // the beginning of the text box (hence not deleting anything)
@@ -1115,6 +1119,7 @@
         TextEntryState.backspace();
         if (TextEntryState.getState() == TextEntryState.STATE_UNDO_COMMIT) {
             revertLastWord(deleteChar);
+            ic.endBatchEdit();
             return;
         } else if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) {
             ic.deleteSurroundingText(mEnteredText.length(), 0);
@@ -1125,6 +1130,7 @@
             }
         }
         mJustRevertedSeparator = null;
+        ic.endBatchEdit();
     }
 
     private void handleShift() {
@@ -1277,9 +1283,10 @@
             mWord.reset();
             return;
         }
-        TypedWordAlternatives entry = new TypedWordAlternatives(result, mWord);
-        // Create a new WordComposer as the old one is being saved for later use
-        mWord = new WordComposer(mWord);
+        // Make a copy of the CharSequence, since it is/could be a mutable CharSequence
+        final String resultCopy = result.toString();
+        TypedWordAlternatives entry = new TypedWordAlternatives(resultCopy,
+                new WordComposer(mWord));
         mWordHistory.add(entry);
     }
 
@@ -1648,7 +1655,8 @@
 
         // Fool the state watcher so that a subsequent backspace will not do a revert
         TextEntryState.typedCharacter((char) KEYCODE_SPACE, true);
-        if (index == 0 && mCorrectionMode > 0 && !mSuggest.isValidWord(suggestion)) {
+        if (index == 0 && mCorrectionMode > 0 && !mSuggest.isValidWord(suggestion)
+                && !mSuggest.isValidWord(suggestion.toString().toLowerCase())) {
             mCandidateView.showAddToDictionaryHint(suggestion);
         }
         if (ic != null) {
@@ -1662,9 +1670,9 @@
             InputConnection ic = getCurrentInputConnection();
             EditingUtil.Range range = new EditingUtil.Range();
             String wordToBeReplaced = EditingUtil.getWordAtCursor(getCurrentInputConnection(),
-                                                                  mWordSeparators, range).trim();
+                    mWordSeparators, range);
             if (!mWordToSuggestions.containsKey(wordToBeReplaced)) {
-              wordToBeReplaced = wordToBeReplaced.toLowerCase();
+                wordToBeReplaced = wordToBeReplaced.toLowerCase();
             }
             if (mWordToSuggestions.containsKey(wordToBeReplaced)) {
                 List<CharSequence> suggestions = mWordToSuggestions.get(wordToBeReplaced);
@@ -1690,9 +1698,6 @@
         InputConnection ic = getCurrentInputConnection();
         if (ic != null) {
             rememberReplacedWord(suggestion);
-            if (mSuggestionShouldReplaceCurrentWord) {
-                EditingUtil.deleteWordAtCursor(ic, getWordSeparators());
-            }
             if (!VoiceInput.DELETE_SYMBOL.equals(suggestion)) {
                 ic.commitText(suggestion, 1);
             }
@@ -1719,9 +1724,8 @@
         }
         if (!mPredicting && isCursorTouchingWord()) {
             EditingUtil.Range range = new EditingUtil.Range();
-            CharSequence touching =
-                EditingUtil.getWordAtCursor(getCurrentInputConnection(), mWordSeparators,
-                        range);
+            CharSequence touching = EditingUtil.getWordAtCursor(getCurrentInputConnection(),
+                    mWordSeparators, range);
             if (touching != null && touching.length() > 1) {
                 if (mWordSeparators.indexOf(touching.charAt(touching.length() - 1)) > 0) {
                     touching = touching.toString().substring(0, touching.length() - 1);
@@ -1782,7 +1786,7 @@
                             foundWord);
                     showCorrections(alternatives);
                     if (foundWord != null) {
-                        mWord = foundWord;
+                        mWord = new WordComposer(foundWord);
                     } else {
                         mWord.reset();
                     }
@@ -1815,6 +1819,7 @@
     private void underlineWord(CharSequence word, int left, int right) {
         InputConnection ic = getCurrentInputConnection();
         if (ic == null) return;
+        ic.finishComposingText();
         ic.deleteSurroundingText(left, right);
         ic.setComposingText(word, 1);
         ic.setSelection(mLastSelectionStart, mLastSelectionStart);
@@ -1859,7 +1864,6 @@
         if (!mPredicting && length > 0) {
             final InputConnection ic = getCurrentInputConnection();
             mPredicting = true;
-            ic.beginBatchEdit();
             mJustRevertedSeparator = ic.getTextBeforeCursor(1, 0);
             if (deleteChar) ic.deleteSurroundingText(1, 0);
             int toDelete = mCommittedLength;
@@ -1871,7 +1875,6 @@
             ic.deleteSurroundingText(toDelete, 0);
             ic.setComposingText(mComposing, 1);
             TextEntryState.backspace();
-            ic.endBatchEdit();
             postUpdateSuggestions();
         } else {
             sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index e2573a0..1ea7484 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -55,7 +55,9 @@
         mTypedWord = new StringBuilder(copy.mTypedWord);
         mCapsCount = copy.mCapsCount;
         mAutoCapitalized = copy.mAutoCapitalized;
+        mIsCapitalized = copy.mIsCapitalized;
     }
+
     /**
      * Clear out the keys registered so far.
      */