Home | History | Annotate | Download | only in latin
      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