KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > ui > texteditor > HippieCompleteAction


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  * IBM Corporation - fixes and cleaning
11  *******************************************************************************/

12 package org.eclipse.ui.texteditor;
13
14 import java.util.ArrayList JavaDoc;
15 import java.util.List JavaDoc;
16 import java.util.ResourceBundle JavaDoc;
17
18 import org.eclipse.jface.text.BadLocationException;
19 import org.eclipse.jface.text.IDocument;
20 import org.eclipse.jface.text.IRewriteTarget;
21 import org.eclipse.jface.text.ITextSelection;
22 import org.eclipse.jface.text.source.ISourceViewer;
23
24 import org.eclipse.core.runtime.Assert;
25 import org.eclipse.core.runtime.IStatus;
26 import org.eclipse.core.runtime.Status;
27 import org.eclipse.ui.IEditorInput;
28 import org.eclipse.ui.IEditorPart;
29 import org.eclipse.ui.IEditorReference;
30 import org.eclipse.ui.IWorkbenchWindow;
31 import org.eclipse.ui.internal.texteditor.CompoundEditExitStrategy;
32 import org.eclipse.ui.internal.texteditor.HippieCompletionEngine;
33 import org.eclipse.ui.internal.texteditor.ICompoundEditListener;
34 import org.eclipse.ui.internal.texteditor.TextEditorPlugin;
35
36 /**
37  * This class implements the emacs style completion action. Completion action is
38  * a stateful action, as the user may invoke it several times in a row in order
39  * to scroll the possible completions.
40  *
41  * TODO: Sort by editor type
42  * TODO: Provide history option
43  *
44  * @since 3.1
45  * @author Genady Beryozkin, me@genady.org
46  */

47 final class HippieCompleteAction extends TextEditorAction {
48
49     /**
50      * This class represents the state of the last completion process. Each time
51      * the user moves to a new position and calls this action an instance of
52      * this inner class is created and saved in
53      * {@link HippieCompleteAction#fLastCompletion}.
54      */

55     private static class CompletionState {
56
57         /** The length of the last suggestion string */
58         int length;
59
60         /** The index of next suggestion (index into the suggestion array) */
61         int nextSuggestion;
62
63         /** The caret position at which we insert the suggestions */
64         final int startOffset;
65
66         /**
67          * The list of suggestions that was computed when the completion action
68          * was first invoked
69          */

70         final String JavaDoc[] suggestions;
71
72         /**
73          * Create a new completion state object
74          *
75          * @param suggestions the array of possible completions
76          * @param startOffset the position in the parent document at which the
77          * completions will be inserted.
78          */

79         CompletionState(String JavaDoc[] suggestions, int startOffset) {
80             this.suggestions= suggestions;
81             this.startOffset= startOffset;
82             length= 0;
83             nextSuggestion= 0;
84         }
85
86         /**
87          * Advances the completion state to represent the next completion.
88          */

89         public void advance() {
90             length= suggestions[nextSuggestion].length();
91             nextSuggestion= (nextSuggestion + 1) % suggestions.length;
92         }
93     }
94
95     /**
96      * The document that will be manipulated (currently open in the editor)
97      */

98     private IDocument fDocument;
99
100     /**
101      * The completion state that is used to continue the iteration over
102      * completion suggestions
103      */

104     private CompletionState fLastCompletion= null;
105
106     /**
107      * The completion engine
108      */

109     private final HippieCompletionEngine fEngine= new HippieCompletionEngine();
110
111     /** The compound edit exit strategy. */
112     private final CompoundEditExitStrategy fExitStrategy= new CompoundEditExitStrategy(ITextEditorActionDefinitionIds.HIPPIE_COMPLETION);
113     
114     /**
115      * Creates a new action.
116      *
117      * @param bundle the resource bundle
118      * @param prefix a prefix to be prepended to the various resource keys
119      * (described in <code>ResourceAction</code> constructor), or
120      * <code>null</code> if none
121      * @param editor the text editor
122      */

123     HippieCompleteAction(ResourceBundle JavaDoc bundle, String JavaDoc prefix, ITextEditor editor) {
124         super(bundle, prefix, editor);
125         fExitStrategy.addCompoundListener(new ICompoundEditListener() {
126             public void endCompoundEdit() {
127                 clearState();
128             }
129         });
130     }
131
132     /**
133      * Invalidates the cached completions, removes all registered listeners and
134      * sets the cached document to <code>null</code>.
135      */

136     private void clearState() {
137         fLastCompletion= null;
138
139         ITextEditor editor= getTextEditor();
140         
141         if (editor != null) {
142             IRewriteTarget target= (IRewriteTarget) editor.getAdapter(IRewriteTarget.class);
143             if (target != null) {
144                 fExitStrategy.disarm();
145                 target.endCompoundChange();
146             }
147         }
148
149         fDocument= null;
150     }
151
152     /**
153      * Perform the next completion.
154      */

155     private void completeNext() {
156         try {
157             fDocument.replace(fLastCompletion.startOffset, fLastCompletion.length, fLastCompletion.suggestions[fLastCompletion.nextSuggestion]);
158         } catch (BadLocationException e) {
159             // we should never get here. different from other places to notify the user.
160
log(e);
161             clearState();
162             return;
163         }
164         
165         // advance the suggestion state
166
fLastCompletion.advance();
167         
168         // move the caret to the insertion point
169
ISourceViewer sourceViewer= ((AbstractTextEditor) getTextEditor()).getSourceViewer();
170         sourceViewer.setSelectedRange(fLastCompletion.startOffset + fLastCompletion.length, 0);
171         sourceViewer.revealRange(fLastCompletion.startOffset, fLastCompletion.length);
172         
173         fExitStrategy.arm(((AbstractTextEditor) getTextEditor()).getSourceViewer());
174     }
175
176     /**
177      * Return the list of suggestions from the current document. First the
178      * document is searched backwards from the caret position and then forwards.
179      *
180      * @param prefix the completion prefix
181      * @return all possible completions that were found in the current document
182      * @throws BadLocationException if accessing the document fails
183      */

184     private ArrayList JavaDoc createSuggestionsFromOpenDocument(String JavaDoc prefix) throws BadLocationException {
185         int selectionOffset= getSelectionOffset();
186
187         ArrayList JavaDoc completions= new ArrayList JavaDoc();
188         completions.addAll(fEngine.getCompletionsBackwards(fDocument, prefix, selectionOffset));
189         completions.addAll(fEngine.getCompletionsForward(fDocument, prefix, selectionOffset - prefix.length(), true));
190
191         return completions;
192     }
193
194     /**
195      * Returns the document currently displayed in the editor, or
196      * <code>null</code>
197      *
198      * @return the document currently displayed in the editor, or
199      * <code>null</code>
200      */

201     private IDocument getCurrentDocument() {
202         ITextEditor editor= getTextEditor();
203         if (editor == null)
204             return null;
205         IDocumentProvider provider= editor.getDocumentProvider();
206         if (provider == null)
207             return null;
208
209         IDocument document= provider.getDocument(editor.getEditorInput());
210         return document;
211     }
212
213     /**
214      * Return the part of a word before the caret. If the caret is not at a
215      * middle/end of a word, returns null.
216      *
217      * @return the prefix at the current cursor position that will be used in
218      * the search for possible completions
219      * @throws BadLocationException if accessing the document fails
220      */

221     private String JavaDoc getCurrentPrefix() throws BadLocationException {
222         ITextSelection selection= (ITextSelection) getTextEditor().getSelectionProvider().getSelection();
223         if (selection.getLength() > 0) {
224             return null;
225         }
226         return fEngine.getPrefixString(fDocument, selection.getOffset());
227     }
228
229     /**
230      * Returns the current selection (or caret) offset.
231      *
232      * @return the current selection (or caret) offset
233      */

234     private int getSelectionOffset() {
235         return ((ITextSelection) getTextEditor().getSelectionProvider().getSelection()).getOffset();
236     }
237
238     /**
239      * Create the array of suggestions. It scans all open text editors and
240      * prefers suggestions from the currently open editor. It also adds the
241      * empty suggestion at the end.
242      *
243      * @param prefix the prefix to search for
244      * @return the list of all possible suggestions in the currently open
245      * editors
246      * @throws BadLocationException if accessing the current document fails
247      */

248     private String JavaDoc[] getSuggestions(String JavaDoc prefix) throws BadLocationException {
249
250         ArrayList JavaDoc suggestions= createSuggestionsFromOpenDocument(prefix);
251
252         IWorkbenchWindow window= getTextEditor().getSite().getWorkbenchWindow();
253         IEditorReference editorsArray[]= window.getActivePage().getEditorReferences();
254
255         for (int i= 0; i < editorsArray.length; i++) {
256             IEditorPart realEditor= editorsArray[i].getEditor(false);
257             if (realEditor instanceof ITextEditor &&
258                     !realEditor.equals(getTextEditor())) { // realEditor != null
259
ITextEditor textEditor= (ITextEditor)realEditor;
260                 IEditorInput input= textEditor.getEditorInput();
261                 IDocument doc= textEditor.getDocumentProvider().getDocument(input);
262
263                 suggestions.addAll(fEngine.getCompletionsForward(doc, prefix, 0, false));
264             }
265         }
266         // add the empty suggestion
267
suggestions.add(""); //$NON-NLS-1$
268

269         List JavaDoc uniqueSuggestions= fEngine.makeUnique(suggestions);
270
271         return (String JavaDoc[]) uniqueSuggestions.toArray(new String JavaDoc[0]);
272     }
273
274     /**
275      * Returns <code>true</code> if the current completion state is still
276      * valid given the current document and selection.
277      *
278      * @return <code>true</code> if the cached state is valid,
279      * <code>false</code> otherwise
280      */

281     private boolean isStateValid() {
282         return fDocument != null
283                 && fDocument.equals(getCurrentDocument())
284                 && fLastCompletion != null
285                 && fLastCompletion.startOffset + fLastCompletion.length == getSelectionOffset();
286     }
287
288     /**
289      * Notifies the user that there are no suggestions.
290      */

291     private void notifyUser() {
292         // TODO notify via status line?
293
getTextEditor().getSite().getShell().getDisplay().beep();
294     }
295
296     /*
297      * @see org.eclipse.jface.action.Action#run()
298      */

299     public void run() {
300         if (!validateEditorInputState())
301             return;
302
303         if (!isStateValid())
304             updateState();
305
306         if (isStateValid())
307             completeNext();
308     }
309
310     /*
311      * @see org.eclipse.jface.action.IAction#isEnabled()
312      */

313     public boolean isEnabled() {
314         return canModifyEditor();
315     }
316
317     /*
318      * @see org.eclipse.ui.texteditor.TextEditorAction#setEditor(org.eclipse.ui.texteditor.ITextEditor)
319      */

320     public void setEditor(ITextEditor editor) {
321         clearState(); // make sure to remove listers before the editor changes!
322
super.setEditor(editor);
323     }
324
325     /**
326      * Update the completion state. The completion cache is updated with the
327      * completions based on the currently displayed document and the current
328      * selection. To track the validity of the cached state, listeners are
329      * registered with the editor and document, and the current document is
330      * cached.
331      */

332     private void updateState() {
333         Assert.isNotNull(getTextEditor());
334
335         clearState();
336
337         IDocument document= getCurrentDocument();
338         if (document != null) {
339             fDocument= document;
340
341             String JavaDoc[] suggestions;
342             try {
343                 String JavaDoc prefix= getCurrentPrefix();
344                 if (prefix == null) {
345                     notifyUser();
346                     return;
347                 }
348                 suggestions= getSuggestions(prefix);
349             } catch (BadLocationException e) {
350                 log(e);
351                 return;
352             }
353
354             // if it is single empty suggestion
355
if (suggestions.length == 1) {
356                 notifyUser();
357                 return;
358             }
359
360             IRewriteTarget target= (IRewriteTarget) getTextEditor().getAdapter(IRewriteTarget.class);
361             if (target != null)
362                 target.beginCompoundChange();
363             
364             fLastCompletion= new CompletionState(suggestions, getSelectionOffset());
365         }
366     }
367
368     /**
369      * Logs the exception.
370      *
371      * @param e the exception
372      */

373     private void log(BadLocationException e) {
374         String JavaDoc msg= e.getLocalizedMessage();
375         if (msg == null)
376             msg= "unable to access the document"; //$NON-NLS-1$
377
TextEditorPlugin.getDefault().getLog().log(new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, IStatus.OK, msg, e));
378     }
379 }
380
Popular Tags