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.emacsCompletion.description = Context insensitive completion
156
command.emacsCompletion.name = Text 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 (+12 lines)
Lines 425-430 Link Here
425
            categoryId="org.eclipse.ui.category.edit"
425
            categoryId="org.eclipse.ui.category.edit"
426
            id="org.eclipse.ui.edit.text.toggleInsertMode">
426
            id="org.eclipse.ui.edit.text.toggleInsertMode">
427
      </command>
427
      </command>
428
      <command
429
            name="%command.emacsCompletion.name"
430
            description="%command.emacsCompletion.description"
431
            categoryId="org.eclipse.ui.category.edit"
432
            id="org.eclipse.ui.edit.text.emacsCompletion">
433
      </command>
428
	
434
	
429
	  <keyBinding
435
	  <keyBinding
430
	        commandId="org.eclipse.ui.edit.text.delete.line"
436
	        commandId="org.eclipse.ui.edit.text.delete.line"
Lines 788-793 Link Here
788
            commandId="org.eclipse.ui.edit.text.toggleInsertMode"
794
            commandId="org.eclipse.ui.edit.text.toggleInsertMode"
789
            contextId="org.eclipse.ui.textEditorScope"
795
            contextId="org.eclipse.ui.textEditorScope"
790
            keySequence="Ctrl+Shift+Insert"
796
            keySequence="Ctrl+Shift+Insert"
797
            keyConfigurationId="org.eclipse.ui.defaultAcceleratorConfiguration">
798
      </keyBinding>
799
      <keyBinding
800
            commandId="org.eclipse.ui.edit.text.emacsCompletion"
801
            contextId="org.eclipse.ui.textEditorScope"
802
            keySequence="Alt+/"
791
            keyConfigurationId="org.eclipse.ui.defaultAcceleratorConfiguration">
803
            keyConfigurationId="org.eclipse.ui.defaultAcceleratorConfiguration">
792
      </keyBinding>
804
      </keyBinding>
793
      <keyBinding
805
      <keyBinding
(-)src/org/eclipse/ui/texteditor/AbstractTextEditor.java (+8 lines)
Lines 4209-4214 Link Here
4209
		action.setHelpContextId(IAbstractTextEditorHelpContextIds.TOGGLE_INSERT_MODE_ACTION);
4209
		action.setHelpContextId(IAbstractTextEditorHelpContextIds.TOGGLE_INSERT_MODE_ACTION);
4210
		action.setActionDefinitionId(ITextEditorActionDefinitionIds.TOGGLE_INSERT_MODE);
4210
		action.setActionDefinitionId(ITextEditorActionDefinitionIds.TOGGLE_INSERT_MODE);
4211
		setAction(ITextEditorActionConstants.TOGGLE_INSERT_MODE, action);
4211
		setAction(ITextEditorActionConstants.TOGGLE_INSERT_MODE, action);
4212
4213
		action = new EmacsCompleteAction(EditorMessages.getResourceBundle(), "Editor.EmacsCompletion.", this); //$NON-NLS-1$
4214
		// TODO action.setHelpContextId(IAbstractTextEditorHelpContextIds.EMACS_COMPLETION_ACTION);
4215
		action.setActionDefinitionId(ITextEditorActionDefinitionIds.EMACS_COMPLETION);
4216
		setAction(ITextEditorActionConstants.EMACS_COMPLETION, action);
4217
4212
		
4218
		
4213
		markAsContentDependentAction(ITextEditorActionConstants.UNDO, true);
4219
		markAsContentDependentAction(ITextEditorActionConstants.UNDO, true);
4214
		markAsContentDependentAction(ITextEditorActionConstants.REDO, true);
4220
		markAsContentDependentAction(ITextEditorActionConstants.REDO, true);
Lines 4217-4222 Link Here
4217
		markAsContentDependentAction(ITextEditorActionConstants.FIND_PREVIOUS, true);
4223
		markAsContentDependentAction(ITextEditorActionConstants.FIND_PREVIOUS, true);
4218
		markAsContentDependentAction(ITextEditorActionConstants.FIND_INCREMENTAL, true);
4224
		markAsContentDependentAction(ITextEditorActionConstants.FIND_INCREMENTAL, true);
4219
		markAsContentDependentAction(ITextEditorActionConstants.FIND_INCREMENTAL_REVERSE, true);
4225
		markAsContentDependentAction(ITextEditorActionConstants.FIND_INCREMENTAL_REVERSE, true);
4226
		markAsContentDependentAction(ITextEditorActionConstants.EMACS_COMPLETION, true);
4220
		
4227
		
4221
		markAsSelectionDependentAction(ITextEditorActionConstants.CUT, true);
4228
		markAsSelectionDependentAction(ITextEditorActionConstants.CUT, true);
4222
		markAsSelectionDependentAction(ITextEditorActionConstants.COPY, true);
4229
		markAsSelectionDependentAction(ITextEditorActionConstants.COPY, true);
Lines 4226-4231 Link Here
4226
		markAsSelectionDependentAction(ITextEditorActionConstants.SHIFT_RIGHT_TAB, true);
4233
		markAsSelectionDependentAction(ITextEditorActionConstants.SHIFT_RIGHT_TAB, true);
4227
		markAsSelectionDependentAction(ITextEditorActionConstants.UPPER_CASE, true);
4234
		markAsSelectionDependentAction(ITextEditorActionConstants.UPPER_CASE, true);
4228
		markAsSelectionDependentAction(ITextEditorActionConstants.LOWER_CASE, true);
4235
		markAsSelectionDependentAction(ITextEditorActionConstants.LOWER_CASE, true);
4236
		markAsSelectionDependentAction(ITextEditorActionConstants.EMACS_COMPLETION, true);
4229
		
4237
		
4230
		markAsPropertyDependentAction(ITextEditorActionConstants.UNDO, true);
4238
		markAsPropertyDependentAction(ITextEditorActionConstants.UNDO, true);
4231
		markAsPropertyDependentAction(ITextEditorActionConstants.REDO, true);
4239
		markAsPropertyDependentAction(ITextEditorActionConstants.REDO, true);
(-)src/org/eclipse/ui/texteditor/ITextEditorActionConstants.java (+7 lines)
Lines 415-418 Link Here
415
	 * @since 3.0
415
	 * @since 3.0
416
	 */
416
	 */
417
	static final String TOGGLE_INSERT_MODE= "TOGGLE_INSERT_MODE"; //$NON-NLS-1$
417
	static final String TOGGLE_INSERT_MODE= "TOGGLE_INSERT_MODE"; //$NON-NLS-1$
418
419
	/** 
420
	 * Name of the action for emacs style completion. 
421
	 * Value: <code>"EMACS_COMPLETION"</code>
422
	 * @since 3.1
423
	 */
424
	static final String EMACS_COMPLETION= "EMACS_COMPLETION"; //$NON-NLS-1$
418
}
425
}
(-)src/org/eclipse/ui/texteditor/ITextEditorActionDefinitionIds.java (+7 lines)
Lines 447-450 Link Here
447
	 * @since 3.0
447
	 * @since 3.0
448
	 */
448
	 */
449
	public static final String TOGGLE_INSERT_MODE= "org.eclipse.ui.edit.text.toggleInsertMode"; //$NON-NLS-1$
449
	public static final String TOGGLE_INSERT_MODE= "org.eclipse.ui.edit.text.toggleInsertMode"; //$NON-NLS-1$
450
451
	/**
452
	 * Action definition ID of the edit -> text complete action
453
	 * Value: <code>"org.eclipse.ui.edit.text.emacsCompletion"</code>).
454
	 * @since 3.1
455
	 */
456
	public static final String EMACS_COMPLETION= "org.eclipse.ui.edit.text.emacsCompletion"; //$NON-NLS-1$
450
}
457
}
(-)src/org/eclipse/ui/texteditor/EmacsCompleteAction.java (+341 lines)
Added Link Here
1
package org.eclipse.ui.texteditor;
2
3
import java.util.*;
4
5
import org.eclipse.jface.text.*;
6
import org.eclipse.jface.viewers.ISelectionChangedListener;
7
import org.eclipse.jface.viewers.SelectionChangedEvent;
8
import org.eclipse.ui.*;
9
10
/**
11
 * This class implements the emacs style completion action.
12
 * Completion action is a stateful action, as the user may invoke
13
 * it several times in a row in order to scroll the possible completions.
14
 * 
15
 * 
16
 * TODO: Work on backward suggestions
17
 * TODO: Sort by editor type
18
 * TODO: Provide history option
19
 * 
20
 * @author Genady Beryozkin, me@genady.org
21
 */
22
public class EmacsCompleteAction extends TextEditorAction {
23
24
	private IDocument doc;
25
	private ITextEditor editor;
26
27
	/** 
28
	 * The completion state to continue
29
	 * the iteration over suggestions
30
	 */
31
	private CompletionState lastCompletion = null;
32
	
33
	private boolean modifyingLock = false;
34
	
35
	SelectionChangeListener selectionListener;
36
	DocumentChangeListerner contentListener;
37
38
	/* (non-Javadoc)
39
	 * @see org.eclipse.ui.texteditor.TextEditorAction#setEditor(org.eclipse.ui.texteditor.ITextEditor)
40
	 */
41
	public void setEditor(ITextEditor editor) {
42
		super.setEditor(editor);
43
44
		if (this.editor != null) {
45
			this.editor.getSelectionProvider().removeSelectionChangedListener(selectionListener);
46
			doc.removeDocumentListener(contentListener);
47
		}
48
		this.editor = editor;
49
		
50
		selectionListener = new SelectionChangeListener();
51
		contentListener = new DocumentChangeListerner();
52
		
53
		IEditorInput input = editor.getEditorInput();
54
		doc = editor.getDocumentProvider().getDocument(input);
55
		editor.getSelectionProvider().addSelectionChangedListener(selectionListener);
56
		doc.addDocumentListener(contentListener);
57
	}
58
	
59
	class SelectionChangeListener implements ISelectionChangedListener {
60
		public void selectionChanged(SelectionChangedEvent event) {
61
			if (!modifyingLock) {
62
				lastCompletion = null;
63
			}
64
		}
65
	}
66
	
67
	class DocumentChangeListerner implements IDocumentListener {
68
		public void documentAboutToBeChanged(DocumentEvent event) {
69
		}
70
71
		public void documentChanged(DocumentEvent event) {
72
			if (!modifyingLock) {
73
				lastCompletion = null;
74
			}
75
		}
76
	}	
77
	
78
	/**
79
	 * Perform the next completion.
80
	 */
81
	private void completeNext() {
82
		// we don't wish to receive events on our own changes
83
		modifyingLock = true;
84
		
85
		try {
86
			doc.replace(lastCompletion.startOffset, lastCompletion.length, 
87
					lastCompletion.suggestions[lastCompletion.nextSuggestion]);
88
		} catch (BadLocationException e) {
89
			// we should never get here
90
			throw new IllegalStateException(e.toString());
91
		}
92
93
		// advance the suggestion state
94
		lastCompletion.advance();
95
96
		// move the cursor to the insertion point
97
		getTextEditor().getSelectionProvider().
98
				setSelection(new TextSelection(lastCompletion.startOffset+lastCompletion.length, 0));
99
		
100
		// allow changes
101
		modifyingLock = false;
102
	}
103
104
	
105
	/**
106
	 * This class represents the state of the last completion process.
107
	 * Each time the user moves to a new position and calls this action
108
	 * an instance of this inner classs is created  and saved in
109
	 * {@link #lastCompletion}.
110
	 */
111
	private static class CompletionState {
112
				
113
		/** The list of suggestions that was computed when the completion
114
		 * action was first invoked 
115
		 */
116
		final String[] suggestions;
117
				
118
		/** The caret position at which we insert the suggestions */
119
		final int startOffset;
120
				
121
		/** The length of the last suggestion string */
122
		int length;
123
		
124
		/** The index of next suggestion (index into the suggestion array) */
125
		int nextSuggestion;
126
		
127
		CompletionState(String[] suggestions, int startOffset) {
128
			this.suggestions = suggestions;
129
			this.startOffset = startOffset;
130
			length = 0;
131
			nextSuggestion = 0;
132
		}
133
		
134
		public void advance() {
135
			length = suggestions[nextSuggestion].length();
136
			nextSuggestion = (nextSuggestion + 1) % suggestions.length;	
137
		}
138
	}
139
140
	/**
141
	 * @param bundle the resource bundle
142
	 * @param prefix a prefix to be prepended to the various resource keys
143
	 *   (described in <code>ResourceAction</code> constructor), or 
144
	 *   <code>null</code> if none
145
	 * @param editor the text editor
146
	 */
147
	protected EmacsCompleteAction(ResourceBundle bundle, String prefix, ITextEditor editor) {
148
		super(bundle, prefix, editor);
149
	}
150
151
	/* (non-Javadoc)
152
	 * @see org.eclipse.jface.action.Action#run()
153
	 */
154
	public void run() {
155
		if (lastCompletion != null) 
156
		{
157
			completeNext();
158
			return;
159
		} 
160
		String prefix = getCurrentPrefix();
161
		if (prefix == null) {
162
			editor.getSite().getShell().getDisplay().beep();
163
			return;
164
		}
165
		String[] suggestions = getSuggestions(prefix);
166
		
167
		// if it is single empty suggestion
168
		if (suggestions.length == 1) {
169
			editor.getSite().getShell().getDisplay().beep();
170
			return;
171
		}
172
		lastCompletion = new CompletionState(suggestions, 
173
				((ITextSelection)editor.getSelectionProvider().getSelection()).getOffset());
174
		completeNext();
175
		editor.setHighlightRange(lastCompletion.startOffset, lastCompletion.length, false);
176
		editor.showHighlightRangeOnly(true);
177
		//editor.
178
	}
179
	
180
	
181
	/**
182
	 * Create the array of suggestions. It scan all open text editors
183
	 * and prefers suggestion from the currently open editor.
184
	 * It also addes the empty suggestion at the end.
185
     * 
186
     * @param currEditor The current editor
187
	 */
188
	public String[] getSuggestions(String prefix) {
189
		
190
		// Change the order of open editors, to make the active editor
191
		// to appear first.
192
		IEditorReference editorsArray[] = editor.getSite().getWorkbenchWindow().getActivePage().getEditorReferences();
193
		ArrayList editorsVector = new ArrayList();
194
		for (int i = 0; i < editorsArray.length; i++) {
195
			IEditorPart realEditor = editorsArray[i].getEditor(false);
196
			if (realEditor != null) {
197
				editorsVector.add(realEditor);
198
			}
199
		}
200
		editorsVector.remove(editor);
201
		editorsVector.add(0, editor);
202
		
203
		// collect the suggestions from all open text editors
204
		LinkedList suggestions = new LinkedList();
205
		for (int i = 0; i < editorsVector.size(); i++) {
206
			if (editorsVector.get(i) instanceof ITextEditor) {
207
				ITextEditor textEditor = (ITextEditor) editorsVector.get(i);
208
				IEditorInput input = textEditor.getEditorInput();
209
				IDocument doc = textEditor.getDocumentProvider().getDocument(input);
210
                
211
				try {
212
					suggestions.addAll(getForwardSuggestions(doc, prefix));
213
				} catch (BadLocationException e) {
214
					// TODO Auto-generated catch block
215
					e.printStackTrace();
216
				}
217
			}
218
		}
219
		
220
		makeUnique(suggestions);
221
				
222
		suggestions.add(""); // empty suggestion
223
		return (String[])suggestions.toArray(new String[0]);
224
	}
225
	
226
	
227
	/**
228
	 * Remove duplicate suggestions, leaving the closest to list head. 
229
	 * @param suggestions
230
	 */
231
	private void makeUnique(LinkedList suggestions) {
232
		HashSet seenAlready = new HashSet();
233
		
234
		for (Iterator i = suggestions.iterator(); i.hasNext();) {
235
			String suggestion = (String) i.next();
236
			if (seenAlready.contains(suggestion)) {
237
				i.remove();
238
			} else {
239
				seenAlready.add(suggestion);
240
			}
241
		}
242
	}
243
244
	/**
245
	 * Copied from {@link FindReplaceDocumentAdapter}.
246
	 * 
247
	 * Converts a non-regex string to a pattern
248
	 * that can be used with the regex search engine.
249
	 * 
250
	 * @param string the non-regex pattern
251
	 * @return the string converted to a regex pattern
252
	 */
253
	private static String asRegPattern(String string) {
254
		StringBuffer out= new StringBuffer(string.length());
255
		boolean quoting= false;
256
		
257
		for (int i= 0, length= string.length(); i < length; i++) {
258
			char ch= string.charAt(i);
259
			if (ch == '\\') {
260
				if (quoting) {
261
					out.append("\\E"); //$NON-NLS-1$
262
					quoting= false;
263
				}
264
				out.append("\\\\"); //$NON-NLS-1$
265
				continue;								
266
			}
267
			if (!quoting) {
268
				out.append("\\Q"); //$NON-NLS-1$
269
				quoting= true;
270
			}
271
			out.append(ch);
272
		}
273
		if (quoting)
274
			out.append("\\E"); //$NON-NLS-1$
275
		
276
		return out.toString();
277
	}
278
279
	
280
	/**
281
	 * Return the list of completion suggestions the correspond to the provided
282
	 * prefix
283
	 * 
284
	 * @param document
285
	 * @param prefix
286
	 * @return
287
	 * @throws BadLocationException
288
	 */
289
	private static ArrayList getForwardSuggestions(IDocument document, String prefix) throws BadLocationException {
290
		ArrayList res = new ArrayList();
291
	
292
		FindReplaceDocumentAdapter searcher = new 
293
			FindReplaceDocumentAdapter(document);
294
		
295
		// search only at word boundaries 
296
		String searchPattern = "\\b" + asRegPattern(prefix);
297
		
298
		IRegion reg = searcher.find(0, searchPattern, true, true, false, true);
299
		while (reg != null) {
300
			// try to complete to a word
301
			IRegion word = searcher.find(reg.getOffset(), "\\w+", true, true, false, true);
302
			if (word.getLength() > reg.getLength() ) { // empty suggestion will be added later
303
				String found = document.get(word.getOffset(), word.getLength());
304
				res.add(found.substring(prefix.length()));
305
			}
306
			int nextPos = word.getOffset() + word.getLength();
307
			if (nextPos >= document.getLength() ) {
308
				break;
309
			}
310
			reg = searcher.find(nextPos, searchPattern, true, true, false, true);
311
		}
312
				
313
		return res;
314
	}
315
	
316
	/**
317
	 * Return the part of a word before the caret.
318
	 * If the caret is not at a middle/end of a word, 
319
	 * returns null.
320
	 */
321
	public String getCurrentPrefix() {
322
		ITextSelection selection = (ITextSelection)editor.getSelectionProvider().getSelection();
323
		if (selection.getLength() > 0) {
324
			return null;
325
		}
326
		int pos = selection.getOffset();
327
        String docText = doc.get();
328
        int prevNonAlpha = pos;
329
        while (prevNonAlpha > 0 && 
330
        	    Character.isJavaIdentifierPart(docText.charAt(prevNonAlpha-1))) 
331
        {
332
        	prevNonAlpha--;
333
        }
334
        if (prevNonAlpha != pos) {
335
            return docText.substring(prevNonAlpha, pos);
336
        } else {
337
        	return null;
338
        }
339
340
	}
341
}

Return to bug 11668