1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.android.inputmethod.latin; 18 19 import android.view.inputmethod.ExtractedText; 20 import android.view.inputmethod.ExtractedTextRequest; 21 import android.view.inputmethod.InputConnection; 22 23 import java.util.regex.Pattern; 24 25 /** 26 * Utility methods to deal with editing text through an InputConnection. 27 */ 28 public class EditingUtils { 29 /** 30 * Number of characters we want to look back in order to identify the previous word 31 */ 32 // Provision for a long word pair and a separator 33 private static final int LOOKBACK_CHARACTER_NUM = BinaryDictionary.MAX_WORD_LENGTH * 2 + 1; 34 private static final int INVALID_CURSOR_POSITION = -1; 35 36 private EditingUtils() { 37 // Unintentional empty constructor for singleton. 38 } 39 40 private static int getCursorPosition(InputConnection connection) { 41 if (null == connection) return INVALID_CURSOR_POSITION; 42 final ExtractedText extracted = connection.getExtractedText(new ExtractedTextRequest(), 0); 43 if (extracted == null) { 44 return INVALID_CURSOR_POSITION; 45 } 46 return extracted.startOffset + extracted.selectionStart; 47 } 48 49 /** 50 * @param connection connection to the current text field. 51 * @param separators characters which may separate words 52 * @return the word that surrounds the cursor, including up to one trailing 53 * separator. For example, if the field contains "he|llo world", where | 54 * represents the cursor, then "hello " will be returned. 55 */ 56 public static String getWordAtCursor(InputConnection connection, String separators) { 57 // getWordRangeAtCursor returns null if the connection is null 58 Range r = getWordRangeAtCursor(connection, separators); 59 return (r == null) ? null : r.mWord; 60 } 61 62 /** 63 * Represents a range of text, relative to the current cursor position. 64 */ 65 public static class Range { 66 /** Characters before selection start */ 67 public final int mCharsBefore; 68 69 /** 70 * Characters after selection start, including one trailing word 71 * separator. 72 */ 73 public final int mCharsAfter; 74 75 /** The actual characters that make up a word */ 76 public final String mWord; 77 78 public Range(int charsBefore, int charsAfter, String word) { 79 if (charsBefore < 0 || charsAfter < 0) { 80 throw new IndexOutOfBoundsException(); 81 } 82 this.mCharsBefore = charsBefore; 83 this.mCharsAfter = charsAfter; 84 this.mWord = word; 85 } 86 } 87 88 private static Range getWordRangeAtCursor(InputConnection connection, String sep) { 89 if (connection == null || sep == null) { 90 return null; 91 } 92 CharSequence before = connection.getTextBeforeCursor(1000, 0); 93 CharSequence after = connection.getTextAfterCursor(1000, 0); 94 if (before == null || after == null) { 95 return null; 96 } 97 98 // Find first word separator before the cursor 99 int start = before.length(); 100 while (start > 0 && !isWhitespace(before.charAt(start - 1), sep)) start--; 101 102 // Find last word separator after the cursor 103 int end = -1; 104 while (++end < after.length() && !isWhitespace(after.charAt(end), sep)) { 105 // Nothing to do here. 106 } 107 108 int cursor = getCursorPosition(connection); 109 if (start >= 0 && cursor + end <= after.length() + before.length()) { 110 String word = before.toString().substring(start, before.length()) 111 + after.toString().substring(0, end); 112 return new Range(before.length() - start, end, word); 113 } 114 115 return null; 116 } 117 118 private static boolean isWhitespace(int code, String whitespace) { 119 return whitespace.contains(String.valueOf((char) code)); 120 } 121 122 private static final Pattern spaceRegex = Pattern.compile("\\s+"); 123 124 public static CharSequence getPreviousWord(InputConnection connection, 125 String sentenceSeperators) { 126 //TODO: Should fix this. This could be slow! 127 if (null == connection) return null; 128 CharSequence prev = connection.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0); 129 return getPreviousWord(prev, sentenceSeperators); 130 } 131 132 // Get the word before the whitespace preceding the non-whitespace preceding the cursor. 133 // Also, it won't return words that end in a separator. 134 // Example : 135 // "abc def|" -> abc 136 // "abc def |" -> abc 137 // "abc def. |" -> abc 138 // "abc def . |" -> def 139 // "abc|" -> null 140 // "abc |" -> null 141 // "abc. def|" -> null 142 public static CharSequence getPreviousWord(CharSequence prev, String sentenceSeperators) { 143 if (prev == null) return null; 144 String[] w = spaceRegex.split(prev); 145 146 // If we can't find two words, or we found an empty word, return null. 147 if (w.length < 2 || w[w.length - 2].length() <= 0) return null; 148 149 // If ends in a separator, return null 150 char lastChar = w[w.length - 2].charAt(w[w.length - 2].length() - 1); 151 if (sentenceSeperators.contains(String.valueOf(lastChar))) return null; 152 153 return w[w.length - 2]; 154 } 155 156 public static CharSequence getThisWord(InputConnection connection, String sentenceSeperators) { 157 if (null == connection) return null; 158 final CharSequence prev = connection.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0); 159 return getThisWord(prev, sentenceSeperators); 160 } 161 162 // Get the word immediately before the cursor, even if there is whitespace between it and 163 // the cursor - but not if there is punctuation. 164 // Example : 165 // "abc def|" -> def 166 // "abc def |" -> def 167 // "abc def. |" -> null 168 // "abc def . |" -> null 169 public static CharSequence getThisWord(CharSequence prev, String sentenceSeperators) { 170 if (prev == null) return null; 171 String[] w = spaceRegex.split(prev); 172 173 // No word : return null 174 if (w.length < 1 || w[w.length - 1].length() <= 0) return null; 175 176 // If ends in a separator, return null 177 char lastChar = w[w.length - 1].charAt(w[w.length - 1].length() - 1); 178 if (sentenceSeperators.contains(String.valueOf(lastChar))) return null; 179 180 return w[w.length - 1]; 181 } 182 } 183