KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > ui > internal > texteditor > HippieCompletionEngine


1 /*******************************************************************************
2  * Copyright (c) 2000, 2006 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * Genady Beryozkin, me@genady.org - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.ui.internal.texteditor;
12
13 import java.util.ArrayList JavaDoc;
14 import java.util.Collections JavaDoc;
15 import java.util.HashSet JavaDoc;
16 import java.util.Iterator JavaDoc;
17 import java.util.List JavaDoc;
18 import java.util.regex.Matcher JavaDoc;
19 import java.util.regex.Pattern JavaDoc;
20
21 import org.eclipse.jface.text.BadLocationException;
22 import org.eclipse.jface.text.FindReplaceDocumentAdapter;
23 import org.eclipse.jface.text.IDocument;
24 import org.eclipse.jface.text.IRegion;
25
26 /**
27  * This class contains the hippie completion engine methods that actually
28  * compute the possible completions.
29  * <p>
30  * This engine is used by the <code>org.eclipse.ui.texteditor.HippieCompleteAction</code>.
31  * </p>
32  *
33  * TODO: Sort by editor type
34  * TODO: Provide history option
35  *
36  * @since 3.1
37  * @author Genady Beryozkin, me@genady.org
38  */

39 public final class HippieCompletionEngine {
40
41     /**
42      * Regular expression that is used to find words.
43      */

44     // unicode identifier part
45
// private static final String COMPLETION_WORD_REGEX= "[\\p{L}[\\p{Mn}[\\p{Pc}[\\p{Nd}[\\p{Nl}]]]]]+"; //$NON-NLS-1$
46
// java identifier part (unicode id part + currency symbols)
47
private static final String JavaDoc COMPLETION_WORD_REGEX= "[\\p{L}[\\p{Mn}[\\p{Pc}[\\p{Nd}[\\p{Nl}[\\p{Sc}]]]]]]+"; //$NON-NLS-1$
48
/**
49      * The pre-compiled word pattern.
50      *
51      * @since 3.2
52      */

53     private static final Pattern JavaDoc COMPLETION_WORD_PATTERN= Pattern.compile(COMPLETION_WORD_REGEX);
54     
55     /**
56      * Word boundary pattern that does not allow searching at the beginning of the document.
57      *
58      * @since 3.2
59      */

60     private static final String JavaDoc NON_EMPTY_COMPLETION_BOUNDARY= "[\\s\\p{Z}[\\p{P}&&[\\P{Pc}]][\\p{S}&&[\\P{Sc}]]]+"; //$NON-NLS-1$
61

62     /**
63      * The word boundary pattern string.
64      *
65      * @since 3.2
66      */

67     private static final String JavaDoc COMPLETION_BOUNDARY= "(^|" + NON_EMPTY_COMPLETION_BOUNDARY + ")"; //$NON-NLS-1$ //$NON-NLS-2$
68
// with a 1.5 JRE, you can do this:
69
// private static final String COMPLETION_WORD_REGEX= "\\p{javaUnicodeIdentifierPart}+"; //$NON-NLS-1$
70
// private static final String COMPLETION_WORD_REGEX= "\\p{javaJavaIdentifierPart}+"; //$NON-NLS-1$
71

72     /**
73      * Is completion case sensitive? Even if set to <code>false</code>, the
74      * case of the prefix won't be changed.
75      */

76     private static final boolean CASE_SENSITIVE= true;
77
78     /**
79      * Creates a new engine.
80      */

81     public HippieCompletionEngine() {
82     }
83
84     /*
85      * Copied from {@link FindReplaceDocumentAdapter#asRegPattern(java.lang.String)}.
86      */

87     /**
88      * Converts a non-regex string to a pattern that can be used with the regex
89      * search engine.
90      *
91      * @param string the non-regex pattern
92      * @return the string converted to a regex pattern
93      */

94     private String JavaDoc asRegPattern(CharSequence JavaDoc string) {
95         StringBuffer JavaDoc out= new StringBuffer JavaDoc(string.length());
96         boolean quoting= false;
97
98         for (int i= 0, length= string.length(); i < length; i++) {
99             char ch= string.charAt(i);
100             if (ch == '\\') {
101                 if (quoting) {
102                     out.append("\\E"); //$NON-NLS-1$
103
quoting= false;
104                 }
105                 out.append("\\\\"); //$NON-NLS-1$
106
continue;
107             }
108             if (!quoting) {
109                 out.append("\\Q"); //$NON-NLS-1$
110
quoting= true;
111             }
112             out.append(ch);
113         }
114         if (quoting)
115             out.append("\\E"); //$NON-NLS-1$
116

117         return out.toString();
118     }
119
120     /**
121      * Return the list of completion suggestions that correspond to the
122      * provided prefix.
123      *
124      * @param document the document to be scanned
125      * @param prefix the prefix to search for
126      * @param firstPosition the initial position in the document that
127      * the search will start from. In order to search from the
128      * beginning of the document use <code>firstPosition=0</code>.
129      * @param currentWordLast if <code>true</code> the word at caret position
130      * should be that last completion. <code>true</code> is good
131      * for searching in the currently open document and <code>false</code>
132      * is good for searching in other documents.
133      * @return a {@link List} of possible completions (as {@link String}s),
134      * excluding the common prefix
135      * @throws BadLocationException if there is some error scanning the
136      * document.
137      */

138     public List JavaDoc getCompletionsForward(IDocument document, CharSequence JavaDoc prefix,
139             int firstPosition, boolean currentWordLast) throws BadLocationException {
140         ArrayList JavaDoc res= new ArrayList JavaDoc();
141         String JavaDoc currentWordCompletion= null; // fix bug 132533
142

143         if (firstPosition == document.getLength()) {
144             return res;
145         }
146
147         FindReplaceDocumentAdapter searcher= new FindReplaceDocumentAdapter(document);
148
149         // search only at word boundaries
150
String JavaDoc searchPattern;
151
152         // unless we are at the beginning of the document, the completion boundary
153
// matches one character. It is enough to move just one character backwards
154
// because the boundary pattern has the (....)+ form.
155
// see HippieCompletionTest#testForwardSearch().
156
if (firstPosition > 0) {
157             firstPosition--;
158             // empty spacing is not permitted now.
159
searchPattern= NON_EMPTY_COMPLETION_BOUNDARY + asRegPattern(prefix);
160         } else {
161             searchPattern= COMPLETION_BOUNDARY + asRegPattern(prefix);
162         }
163         
164         IRegion reg= searcher.find(firstPosition, searchPattern, true, CASE_SENSITIVE, false, true);
165         while (reg != null) {
166             // since the boundary may be of nonzero length
167
int wordSearchPos= reg.getOffset() + reg.getLength() - prefix.length();
168             // try to complete to a word. case is irrelevant here.
169
IRegion word= searcher.find(wordSearchPos, COMPLETION_WORD_REGEX, true, true, false, true);
170             if (word.getLength() > prefix.length() ) { // empty suggestion will be added later
171
String JavaDoc wholeWord= document.get(word.getOffset(), word.getLength());
172                 String JavaDoc completion= wholeWord.substring(prefix.length());
173                 if (currentWordLast && reg.getOffset() == firstPosition) { // we got the word at caret as completion
174
currentWordCompletion= completion; // add it as the last word.
175
} else {
176                     res.add(completion);
177                 }
178             }
179             int nextPos= word.getOffset() + word.getLength();
180             if (nextPos >= document.getLength() ) {
181                 break;
182             }
183             reg= searcher.find(nextPos, searchPattern, true, CASE_SENSITIVE, false, true);
184         }
185         
186         // the word at caret position goes last (bug 132533).
187
if (currentWordCompletion != null) {
188             res.add(currentWordCompletion);
189         }
190
191         return res;
192     }
193
194     /**
195      * Search for possible completions in the backward direction. If there
196      * is a possible completion that begins before <code>firstPosition</code>
197      * but ends after that position, it will not be included in the results.
198      *
199      * @param document the document to be scanned
200      * @param prefix the completion prefix
201      * @param firstPosition the caret position
202      * @return a {@link List} of possible completions ({@link String}s)
203      * from the caret position to the beginning of the document.
204      * The empty suggestion is not included in the results.
205      * @throws BadLocationException if any error occurs
206      */

207     public List JavaDoc getCompletionsBackwards(IDocument document, CharSequence JavaDoc prefix, int firstPosition) throws BadLocationException {
208         ArrayList JavaDoc res= new ArrayList JavaDoc();
209
210         // FindReplaceDocumentAdapter expects the start offset to be before the
211
// actual caret position, probably for compatibility with forward search.
212
if (firstPosition == 0) {
213             return res;
214         }
215
216         FindReplaceDocumentAdapter searcher= new FindReplaceDocumentAdapter(document);
217
218         // search only at word boundaries
219
String JavaDoc searchPattern= COMPLETION_BOUNDARY + asRegPattern(prefix);
220
221         IRegion reg= searcher.find(0, searchPattern, true, CASE_SENSITIVE, false, true);
222         while (reg != null) {
223             // since the boundary may be of nonzero length
224
int wordSearchPos= reg.getOffset() + reg.getLength() - prefix.length();
225             // try to complete to a word. case is of no matter here
226
IRegion word= searcher.find(wordSearchPos, COMPLETION_WORD_REGEX, true, true, false, true);
227             if (word.getOffset() + word.getLength() > firstPosition) {
228                 break;
229             }
230             if (word.getLength() > prefix.length() ) { // empty suggestion will be added later
231
String JavaDoc found= document.get(word.getOffset(), word.getLength());
232                 res.add(found.substring(prefix.length()));
233             }
234             int nextPos= word.getOffset() + word.getLength();
235             if (nextPos >= firstPosition ) { // for efficiency only
236
break;
237             }
238             reg= searcher.find(nextPos, searchPattern, true, CASE_SENSITIVE, false, true);
239         }
240         Collections.reverse(res);
241
242         return res;
243     }
244
245     /**
246      * Returns the text between the provided position and the preceding word boundary.
247      *
248      * @param doc the document that will be scanned.
249      * @param pos the caret position.
250      * @return the text if found, or null.
251      * @throws BadLocationException if an error occurs.
252      * @since 3.2
253      */

254     public String JavaDoc getPrefixString(IDocument doc, int pos) throws BadLocationException {
255         Matcher JavaDoc m= COMPLETION_WORD_PATTERN.matcher(""); //$NON-NLS-1$
256
int prevNonAlpha= pos;
257         while (prevNonAlpha > 0) {
258             m.reset(doc.get(prevNonAlpha-1, pos - prevNonAlpha + 1));
259             if (!m.matches()) {
260                 break;
261             }
262             prevNonAlpha--;
263         }
264         if (prevNonAlpha != pos) {
265             return doc.get(prevNonAlpha, pos - prevNonAlpha);
266         }
267         return null;
268     }
269
270     /**
271      * Remove duplicate suggestions (excluding the prefix), leaving the closest
272      * to list head.
273      *
274      * @param suggestions a list of suggestions ({@link String}).
275      * @return a list of unique completion suggestions.
276      */

277     public List JavaDoc makeUnique(List JavaDoc suggestions) {
278         HashSet JavaDoc seenAlready= new HashSet JavaDoc();
279         ArrayList JavaDoc uniqueSuggestions= new ArrayList JavaDoc();
280
281         for (Iterator JavaDoc i= suggestions.iterator(); i.hasNext();) {
282             String JavaDoc suggestion= (String JavaDoc) i.next();
283             if (!seenAlready.contains(suggestion)) {
284                 seenAlready.add(suggestion);
285                 uniqueSuggestions.add(suggestion);
286             }
287         }
288         return uniqueSuggestions;
289     }
290 }
291
Popular Tags