Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
View | Details | Raw Unified | Return to bug 11668 | Differences between
and this patch

Collapse All | Expand All

(-)plugin.properties (+2 lines)
Lines 152-157 Link Here
152
command.toggleOverwrite.name = Toggle Overwrite
152
command.toggleOverwrite.name = Toggle Overwrite
153
command.toggleInsertMode.description = Toggle insert mode
153
command.toggleInsertMode.description = Toggle insert mode
154
command.toggleInsertMode.name = Toggle Insert Mode
154
command.toggleInsertMode.name = Toggle Insert Mode
155
command.hippieCompletion.description = Context insensitive completion
156
command.hippieCompletion.name = Hippie Completion
155
command.windowEnd.description = Go to the end of the window
157
command.windowEnd.description = Go to the end of the window
156
command.windowEnd.name = Window End
158
command.windowEnd.name = Window End
157
command.windowStart.description = Go to the start of the window
159
command.windowStart.description = Go to the start of the window
(-)plugin.xml (+13 lines)
Lines 426-431 Link Here
426
            categoryId="org.eclipse.ui.category.edit"
426
            categoryId="org.eclipse.ui.category.edit"
427
            id="org.eclipse.ui.edit.text.toggleInsertMode">
427
            id="org.eclipse.ui.edit.text.toggleInsertMode">
428
      </command>
428
      </command>
429
      <command
430
            name="%command.hippieCompletion.name"
431
            description="%command.hippieCompletion.description"
432
            categoryId="org.eclipse.ui.category.edit"
433
            id="org.eclipse.ui.edit.text.hippieCompletion">
434
      </command>
435
      <!-- TODO Keybinding -->
436
      <keyBinding
437
            commandId="org.eclipse.ui.edit.text.hippieCompletion"
438
            contextId="org.eclipse.ui.textEditorScope"
439
            keySequence="Alt+/"
440
            keyConfigurationId="org.eclipse.ui.defaultAcceleratorConfiguration">
441
       </keyBinding>
429
	
442
	
430
	  <keyBinding
443
	  <keyBinding
431
	        commandId="org.eclipse.ui.edit.text.delete.line"
444
	        commandId="org.eclipse.ui.edit.text.delete.line"
(-)src/org/eclipse/ui/texteditor/AbstractTextEditor.java (+6 lines)
Lines 4272-4277 Link Here
4272
		action.setActionDefinitionId(ITextEditorActionDefinitionIds.TOGGLE_INSERT_MODE);
4272
		action.setActionDefinitionId(ITextEditorActionDefinitionIds.TOGGLE_INSERT_MODE);
4273
		setAction(ITextEditorActionConstants.TOGGLE_INSERT_MODE, action);
4273
		setAction(ITextEditorActionConstants.TOGGLE_INSERT_MODE, action);
4274
4274
4275
		action = new HippieCompleteAction(EditorMessages.getResourceBundle(), "Editor.HippieCompletion.", this); //$NON-NLS-1$
4276
		// TODO action.setHelpContextId(IAbstractTextEditorHelpContextIds.HIPPIE_COMPLETION_ACTION);
4277
		action.setActionDefinitionId(ITextEditorActionDefinitionIds.HIPPIE_COMPLETION);
4278
		setAction(ITextEditorActionConstants.HIPPIE_COMPLETION, action);
4279
		
4275
		PropertyDialogAction openProperties= new PropertyDialogAction(
4280
		PropertyDialogAction openProperties= new PropertyDialogAction(
4276
				getSite().getShell(),
4281
				getSite().getShell(),
4277
				new ISelectionProvider() {
4282
				new ISelectionProvider() {
Lines 4295-4300 Link Here
4295
		markAsContentDependentAction(ITextEditorActionConstants.FIND_PREVIOUS, true);
4300
		markAsContentDependentAction(ITextEditorActionConstants.FIND_PREVIOUS, true);
4296
		markAsContentDependentAction(ITextEditorActionConstants.FIND_INCREMENTAL, true);
4301
		markAsContentDependentAction(ITextEditorActionConstants.FIND_INCREMENTAL, true);
4297
		markAsContentDependentAction(ITextEditorActionConstants.FIND_INCREMENTAL_REVERSE, true);
4302
		markAsContentDependentAction(ITextEditorActionConstants.FIND_INCREMENTAL_REVERSE, true);
4303
        markAsContentDependentAction(ITextEditorActionConstants.HIPPIE_COMPLETION, true);
4298
		
4304
		
4299
		markAsSelectionDependentAction(ITextEditorActionConstants.CUT, true);
4305
		markAsSelectionDependentAction(ITextEditorActionConstants.CUT, true);
4300
		markAsSelectionDependentAction(ITextEditorActionConstants.COPY, true);
4306
		markAsSelectionDependentAction(ITextEditorActionConstants.COPY, true);
(-)src/org/eclipse/ui/texteditor/ITextEditorActionConstants.java (+7 lines)
Lines 517-520 Link Here
517
	 * @since 3.1
517
	 * @since 3.1
518
	 */
518
	 */
519
	static final String QUICKDIFF_TOGGLE= "QuickDiff.Toggle"; //$NON-NLS-1$
519
	static final String QUICKDIFF_TOGGLE= "QuickDiff.Toggle"; //$NON-NLS-1$
520
521
	/** 
522
	 * Name of the action for emacs style hippie completion. 
523
	 * Value: <code>"HIPPIE_COMPLETION"</code>
524
	 * @since 3.1
525
	 */
526
	static final String HIPPIE_COMPLETION= "HIPPIE_COMPLETION"; //$NON-NLS-1$
520
}
527
}
(-)src/org/eclipse/ui/texteditor/ITextEditorActionDefinitionIds.java (+7 lines)
Lines 485-488 Link Here
485
	 * @since 3.1
485
	 * @since 3.1
486
	 */
486
	 */
487
	static final String LINENUMBER_TOGGLE= "org.eclipse.ui.editors.lineNumberToggle"; //$NON-NLS-1$
487
	static final String LINENUMBER_TOGGLE= "org.eclipse.ui.editors.lineNumberToggle"; //$NON-NLS-1$
488
489
	/**
490
	 * Action definition ID of the edit -> text complete action
491
	 * Value: <code>"org.eclipse.ui.edit.text.hippieCompletion"</code>).
492
	 * @since 3.1
493
	 */
494
	public static final String HIPPIE_COMPLETION= "org.eclipse.ui.edit.text.hippieCompletion"; //$NON-NLS-1$
488
}
495
}
(-)src/org/eclipse/ui/texteditor/HippieCompleteAction.java (+412 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2000, 2005 IBM Corporation and others.
3
 * All rights reserved. This program and the accompanying materials 
4
 * are made available under the terms of the Common Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/cpl-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;
15
import java.util.HashSet;
16
import java.util.Iterator;
17
import java.util.LinkedList;
18
import java.util.ResourceBundle;
19
20
import org.eclipse.jface.text.BadLocationException;
21
import org.eclipse.jface.text.DocumentEvent;
22
import org.eclipse.jface.text.FindReplaceDocumentAdapter;
23
import org.eclipse.jface.text.IDocument;
24
import org.eclipse.jface.text.IDocumentListener;
25
import org.eclipse.jface.text.IRegion;
26
import org.eclipse.jface.text.ITextSelection;
27
import org.eclipse.jface.viewers.ISelectionChangedListener;
28
import org.eclipse.jface.viewers.ISelectionProvider;
29
import org.eclipse.jface.viewers.SelectionChangedEvent;
30
31
import org.eclipse.ui.IEditorInput;
32
import org.eclipse.ui.IEditorPart;
33
import org.eclipse.ui.IEditorReference;
34
35
/**
36
 * This class implements the emacs style completion action.
37
 * Completion action is a stateful action, as the user may invoke
38
 * it several times in a row in order to scroll the possible completions.
39
 * 
40
 * 
41
 * TODO: Work on backward suggestions
42
 * TODO: Sort by editor type
43
 * TODO: Provide history option
44
 * 
45
 * @author Genady Beryozkin, me@genady.org
46
 */
47
public class HippieCompleteAction extends TextEditorAction {
48
49
    /**
50
     * The document that will be manipulated
51
     */
52
	private IDocument fDocument;
53
    
54
    /**
55
     * The selection provider associated with the text editor. 
56
     */
57
    private ISelectionProvider fSelectionProvider;
58
59
	/** 
60
	 * The completion state that is used to continue the iteration over suggestions
61
	 */
62
	private CompletionState fLastCompletion= null;
63
	
64
    /**
65
     * Modification lock that will prevent invalidation of the completion state when the
66
     * completion action modifies the document
67
     */
68
	private boolean fModifyingLock= false;
69
	
70
	SelectionChangeListener fSelectionListener;
71
	DocumentChangeListener fContentListener;
72
73
	/* (non-Javadoc)
74
	 * @see org.eclipse.ui.texteditor.TextEditorAction#setEditor(org.eclipse.ui.texteditor.ITextEditor)
75
	 */
76
	public void setEditor(ITextEditor editor) {
77
		super.setEditor(editor);
78
79
		fSelectionListener= new SelectionChangeListener();
80
		fContentListener= new DocumentChangeListener();
81
	}
82
	
83
    /**
84
     * This class accompanies the {@link HippieCompleteAction#checkValidPosition()} method
85
     * to invalidate completion state when the selection changes.
86
     */
87
	class SelectionChangeListener implements ISelectionChangedListener {
88
		public void selectionChanged(SelectionChangedEvent event) {
89
			if (!fModifyingLock) {
90
				fLastCompletion= null;
91
			}
92
		}
93
	}
94
	
95
    /**
96
     * Invalidate the completion state when the document contents changes not
97
     * as the result of the completion action itself. This is needed since
98
     * the {@link HippieCompleteAction#update()} method is only called when the
99
     * {@link HippieCompleteAction#fModifyingLock} is already false.
100
     */
101
	class DocumentChangeListener implements IDocumentListener {
102
		public void documentAboutToBeChanged(DocumentEvent event) {
103
		}
104
105
		public void documentChanged(DocumentEvent event) {
106
			if (!fModifyingLock) {
107
				fLastCompletion= null;
108
			}
109
		}
110
	}	
111
	
112
	/**
113
	 * Perform the next completion.
114
	 */
115
	private void completeNext() {
116
		// we don't wish to receive events on our own changes
117
		fModifyingLock= true;
118
		
119
		try {
120
			fDocument.replace(fLastCompletion.startOffset, fLastCompletion.length, 
121
					fLastCompletion.suggestions[fLastCompletion.nextSuggestion]);
122
		} catch (BadLocationException e) {
123
			// we should never get here
124
			throw new IllegalStateException(e.toString());
125
		}
126
127
		// advance the suggestion state
128
		fLastCompletion.advance();
129
130
		// move the cursor to the insertion point
131
		((AbstractTextEditor) getTextEditor()).getSourceViewer().setSelectedRange(fLastCompletion.startOffset+fLastCompletion.length, 0);
132
		
133
		// allow changes
134
		fModifyingLock= false;
135
	}
136
137
	
138
	/**
139
	 * This class represents the state of the last completion process.
140
	 * Each time the user moves to a new position and calls this action
141
	 * an instance of this inner classs is created  and saved in
142
	 * {@link #fLastCompletion}.
143
	 */
144
	private static class CompletionState {
145
				
146
		/** The list of suggestions that was computed when the completion
147
		 * action was first invoked 
148
		 */
149
		final String[] suggestions;
150
				
151
		/** The caret position at which we insert the suggestions */
152
		final int startOffset;
153
				
154
		/** The length of the last suggestion string */
155
		int length;
156
		
157
		/** The index of next suggestion (index into the suggestion array) */
158
		int nextSuggestion;
159
		
160
		CompletionState(String[] suggestions, int startOffset) {
161
			this.suggestions= suggestions;
162
			this.startOffset= startOffset;
163
			length= 0;
164
			nextSuggestion= 0;
165
		}
166
		
167
		public void advance() {
168
			length= suggestions[nextSuggestion].length();
169
			nextSuggestion= (nextSuggestion + 1) % suggestions.length;	
170
		}
171
	}
172
173
	/**
174
	 * @param bundle the resource bundle
175
	 * @param prefix a prefix to be prepended to the various resource keys
176
	 *   (described in <code>ResourceAction</code> constructor), or 
177
	 *   <code>null</code> if none
178
	 * @param editor the text editor
179
	 */
180
	protected HippieCompleteAction(ResourceBundle bundle, String prefix, ITextEditor editor) {
181
		super(bundle, prefix, editor);
182
	}
183
184
	/* (non-Javadoc)
185
	 * @see org.eclipse.jface.action.Action#run()
186
	 */
187
	public void run() {
188
        checkValidPosition();
189
		if (fLastCompletion != null) 
190
		{
191
			completeNext();
192
			return;
193
		} 
194
		String prefix= getCurrentPrefix();
195
		if (prefix == null) {
196
			getTextEditor().getSite().getShell().getDisplay().beep();
197
			return;
198
		}
199
		String[] suggestions= getSuggestions(prefix);
200
		
201
		// if it is single empty suggestion
202
		if (suggestions.length == 1) {
203
			getTextEditor().getSite().getShell().getDisplay().beep();
204
			return;
205
		}
206
		fLastCompletion= new CompletionState(suggestions, 
207
				((ITextSelection) getTextEditor().getSelectionProvider().getSelection()).getOffset());
208
		completeNext();
209
	}
210
211
    /**
212
     * Invalidate the completion state if the cursor is not located at
213
     * the expected position.
214
     */
215
    private void checkValidPosition() {
216
        if (fLastCompletion != null) {
217
            if (fLastCompletion.startOffset + fLastCompletion.length != 
218
                ((ITextSelection)fSelectionProvider.getSelection()).getOffset()) {
219
                fLastCompletion= null;
220
            }
221
        }
222
    }
223
	
224
	
225
	/**
226
	 * Create the array of suggestions. It scan all open text editors
227
	 * and prefers suggestion from the currently open editor.
228
	 * It also addes the empty suggestion at the end.
229
	 * @param prefix the prefix to search for
230
	 * @return the list of all possible suggestions in the currently open editors
231
	 */
232
	public String[] getSuggestions(String prefix) {
233
		
234
		// Change the order of open editors, to make the active editor
235
		// to appear first.
236
		IEditorReference editorsArray[]= getTextEditor().getSite().getWorkbenchWindow().getActivePage().getEditorReferences();
237
		ArrayList editorsVector= new ArrayList();
238
		for (int i= 0; i < editorsArray.length; i++) {
239
			IEditorPart realEditor= editorsArray[i].getEditor(false);
240
			if (realEditor != null) {
241
				editorsVector.add(realEditor);
242
			}
243
		}
244
		editorsVector.remove(getTextEditor());
245
		editorsVector.add(0, getTextEditor());
246
		
247
		// collect the suggestions from all open text editors
248
		LinkedList suggestions= new LinkedList();
249
		for (int i= 0; i < editorsVector.size(); i++) {
250
			if (editorsVector.get(i) instanceof ITextEditor) {
251
				ITextEditor textEditor= (ITextEditor) editorsVector.get(i);
252
				IEditorInput input= textEditor.getEditorInput();
253
				IDocument doc= textEditor.getDocumentProvider().getDocument(input);
254
                
255
				try {
256
					suggestions.addAll(getForwardSuggestions(doc, prefix));
257
				} catch (BadLocationException e) {
258
                    // do nothing 
259
                    // TODO log the exception
260
				}
261
			}
262
		}
263
		
264
		makeUnique(suggestions);
265
				
266
		// add the empty suggestion
267
		suggestions.add(""); //$NON-NLS-1$
268
		return (String[])suggestions.toArray(new String[0]);
269
	}
270
	
271
	
272
	/**
273
	 * Remove duplicate suggestions (excluding the prefix), leaving the closest to list head. 
274
	 * @param suggestions a modifiable list of suggestions.
275
	 */
276
	private void makeUnique(LinkedList suggestions) {
277
		HashSet seenAlready= new HashSet();
278
		
279
		for (Iterator i= suggestions.iterator(); i.hasNext();) {
280
			String suggestion= (String) i.next();
281
			if (seenAlready.contains(suggestion)) {
282
				i.remove();
283
			} else {
284
				seenAlready.add(suggestion);
285
			}
286
		}
287
	}
288
289
	/**
290
	 * Copied from {@link FindReplaceDocumentAdapter}.
291
	 * 
292
	 * Converts a non-regex string to a pattern
293
	 * that can be used with the regex search engine.
294
	 * 
295
	 * @param string the non-regex pattern
296
	 * @return the string converted to a regex pattern
297
	 */
298
	private static String asRegPattern(String string) {
299
		StringBuffer out= new StringBuffer(string.length());
300
		boolean quoting= false;
301
		
302
		for (int i= 0, length= string.length(); i < length; i++) {
303
			char ch= string.charAt(i);
304
			if (ch == '\\') {
305
				if (quoting) {
306
					out.append("\\E"); //$NON-NLS-1$
307
					quoting= false;
308
				}
309
				out.append("\\\\"); //$NON-NLS-1$
310
				continue;								
311
			}
312
			if (!quoting) {
313
				out.append("\\Q"); //$NON-NLS-1$
314
				quoting= true;
315
			}
316
			out.append(ch);
317
		}
318
		if (quoting)
319
			out.append("\\E"); //$NON-NLS-1$
320
		
321
		return out.toString();
322
	}
323
324
	
325
	/**
326
	 * Return the list of completion suggestions the correspond to the provided
327
	 * prefix
328
	 * 
329
	 * @param document
330
	 * @param prefix
331
	 * @return an {@link ArrayList} of possible completions, excluding the common prefix
332
	 * @throws BadLocationException
333
	 */
334
	private static ArrayList getForwardSuggestions(IDocument document, String prefix) throws BadLocationException {
335
		ArrayList res= new ArrayList();
336
	
337
		FindReplaceDocumentAdapter searcher= new 
338
			FindReplaceDocumentAdapter(document);
339
		
340
		// search only at word boundaries 
341
		String searchPattern= "\\b" + asRegPattern(prefix); //$NON-NLS-1$
342
		
343
		IRegion reg= searcher.find(0, searchPattern, true, true, false, true);
344
		while (reg != null) {
345
			// try to complete to a word
346
			IRegion word= searcher.find(reg.getOffset(), "\\w+", true, true, false, true); //$NON-NLS-1$
347
			if (word.getLength() > reg.getLength() ) { // empty suggestion will be added later
348
				String found= document.get(word.getOffset(), word.getLength());
349
				res.add(found.substring(prefix.length()));
350
			}
351
			int nextPos= word.getOffset() + word.getLength();
352
			if (nextPos >= document.getLength() ) {
353
				break;
354
			}
355
			reg= searcher.find(nextPos, searchPattern, true, true, false, true);
356
		}
357
				
358
		return res;
359
	}
360
	
361
	/**
362
	 * Return the part of a word before the caret.
363
	 * If the caret is not at a middle/end of a word, 
364
	 * returns null.
365
	 * @return the prefix at the current cursor position that will be used in the search for possible completions
366
	 */
367
	private String getCurrentPrefix() {
368
		ITextSelection selection= (ITextSelection) getTextEditor().getSelectionProvider().getSelection();
369
		if (selection.getLength() > 0) {
370
			return null;
371
		}
372
		int pos= selection.getOffset();
373
        String docText= fDocument.get();
374
        int prevNonAlpha= pos;
375
        while (prevNonAlpha > 0 && 
376
        	    Character.isJavaIdentifierPart(docText.charAt(prevNonAlpha-1))) 
377
        {
378
        	prevNonAlpha--;
379
        }
380
        if (prevNonAlpha != pos) {
381
            return docText.substring(prevNonAlpha, pos);
382
        }
383
        return null;
384
	}
385
    
386
    /* (non-Javadoc)
387
     * @see org.eclipse.ui.texteditor.TextEditorAction#update()
388
     */
389
    public void update() {
390
        super.update();
391
        if (fDocument != null) {
392
            fDocument.removeDocumentListener(fContentListener);
393
        }
394
        if (fSelectionProvider != null) {
395
            fSelectionProvider.removeSelectionChangedListener(fSelectionListener);
396
        }
397
398
        ITextEditor editor= getTextEditor();
399
        IEditorInput input= editor.getEditorInput();
400
        
401
        fSelectionProvider= editor.getSelectionProvider(); 
402
        fSelectionProvider.addSelectionChangedListener(fSelectionListener);
403
        
404
        IDocument oldDocument = fDocument;
405
        fDocument= editor.getDocumentProvider().getDocument(input);
406
        fDocument.addDocumentListener(fContentListener);
407
        
408
        if (fDocument != oldDocument) {
409
            fLastCompletion = null; // invalidate completion state on editor reuse 
410
        }
411
    }
412
}

Return to bug 11668