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 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.emacsCompletion.name"
431
            description="%command.emacsCompletion.description"
432
            categoryId="org.eclipse.ui.category.edit"
433
            id="org.eclipse.ui.edit.text.emacsCompletion">
434
      </command>
435
      <keyBinding
436
            commandId="org.eclipse.ui.edit.text.emacsCompletion"
437
            contextId="org.eclipse.ui.textEditorScope"
438
            keySequence="Alt+/"
439
            keyConfigurationId="org.eclipse.ui.defaultAcceleratorConfiguration">
440
       </keyBinding>
429
	
441
	
430
	  <keyBinding
442
	  <keyBinding
431
	        commandId="org.eclipse.ui.edit.text.delete.line"
443
	        commandId="org.eclipse.ui.edit.text.delete.line"
(-)src/org/eclipse/ui/texteditor/AbstractTextEditor.java (+5 lines)
Lines 4282-4287 Link Here
4282
		action.setActionDefinitionId(ITextEditorActionDefinitionIds.TOGGLE_INSERT_MODE);
4282
		action.setActionDefinitionId(ITextEditorActionDefinitionIds.TOGGLE_INSERT_MODE);
4283
		setAction(ITextEditorActionConstants.TOGGLE_INSERT_MODE, action);
4283
		setAction(ITextEditorActionConstants.TOGGLE_INSERT_MODE, action);
4284
4284
4285
		action = new EmacsCompleteAction(EditorMessages.getResourceBundle(), "Editor.EmacsCompletion.", this); //$NON-NLS-1$
4286
		// TODO action.setHelpContextId(IAbstractTextEditorHelpContextIds.EMACS_COMPLETION_ACTION);
4287
		action.setActionDefinitionId(ITextEditorActionDefinitionIds.EMACS_COMPLETION);
4288
		setAction(ITextEditorActionConstants.EMACS_COMPLETION, action);
4289
		
4285
		PropertyDialogAction openProperties= new PropertyDialogAction(
4290
		PropertyDialogAction openProperties= new PropertyDialogAction(
4286
				getSite().getShell(),
4291
				getSite().getShell(),
4287
				new ISelectionProvider() {
4292
				new ISelectionProvider() {
(-)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 completion. 
523
	 * Value: <code>"EMACS_COMPLETION"</code>
524
	 * @since 3.1
525
	 */
526
	static final String EMACS_COMPLETION= "EMACS_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.emacsCompletion"</code>).
492
	 * @since 3.1
493
	 */
494
	public static final String EMACS_COMPLETION= "org.eclipse.ui.edit.text.emacsCompletion"; //$NON-NLS-1$
488
}
495
}
(-)src/org/eclipse/ui/texteditor/EmacsCompleteAction.java (+360 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.viewers.ISelectionChangedListener;
21
import org.eclipse.jface.viewers.SelectionChangedEvent;
22
23
import org.eclipse.jface.text.BadLocationException;
24
import org.eclipse.jface.text.DocumentEvent;
25
import org.eclipse.jface.text.FindReplaceDocumentAdapter;
26
import org.eclipse.jface.text.IDocument;
27
import org.eclipse.jface.text.IDocumentListener;
28
import org.eclipse.jface.text.IRegion;
29
import org.eclipse.jface.text.ITextSelection;
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 EmacsCompleteAction extends TextEditorAction {
48
49
	private IDocument doc;
50
51
	/** 
52
	 * The completion state to continue
53
	 * the iteration over suggestions
54
	 */
55
	private CompletionState lastCompletion = null;
56
	
57
	private boolean modifyingLock = false;
58
	
59
	SelectionChangeListener selectionListener;
60
	DocumentChangeListerner contentListener;
61
62
	/* (non-Javadoc)
63
	 * @see org.eclipse.ui.texteditor.TextEditorAction#setEditor(org.eclipse.ui.texteditor.ITextEditor)
64
	 */
65
	public void setEditor(ITextEditor editor) {
66
		ITextEditor old= getTextEditor();
67
		if (old != null) {
68
			old.getSelectionProvider().removeSelectionChangedListener(selectionListener);
69
			doc.removeDocumentListener(contentListener);
70
		}
71
		super.setEditor(editor);
72
73
		selectionListener = new SelectionChangeListener();
74
		contentListener = new DocumentChangeListerner();
75
		
76
		IEditorInput input = editor.getEditorInput();
77
		doc = editor.getDocumentProvider().getDocument(input);
78
		editor.getSelectionProvider().addSelectionChangedListener(selectionListener);
79
		doc.addDocumentListener(contentListener);
80
	}
81
	
82
	class SelectionChangeListener implements ISelectionChangedListener {
83
		public void selectionChanged(SelectionChangedEvent event) {
84
			if (!modifyingLock) {
85
				lastCompletion = null;
86
			}
87
		}
88
	}
89
	
90
	class DocumentChangeListerner implements IDocumentListener {
91
		public void documentAboutToBeChanged(DocumentEvent event) {
92
		}
93
94
		public void documentChanged(DocumentEvent event) {
95
			if (!modifyingLock) {
96
				lastCompletion = null;
97
			}
98
		}
99
	}	
100
	
101
	/**
102
	 * Perform the next completion.
103
	 */
104
	private void completeNext() {
105
		// we don't wish to receive events on our own changes
106
		modifyingLock = true;
107
		
108
		try {
109
			doc.replace(lastCompletion.startOffset, lastCompletion.length, 
110
					lastCompletion.suggestions[lastCompletion.nextSuggestion]);
111
		} catch (BadLocationException e) {
112
			// we should never get here
113
			throw new IllegalStateException(e.toString());
114
		}
115
116
		// advance the suggestion state
117
		lastCompletion.advance();
118
119
		// move the cursor to the insertion point
120
		((AbstractTextEditor) getTextEditor()).getSourceViewer().setSelectedRange(lastCompletion.startOffset+lastCompletion.length, 0);
121
		
122
		// allow changes
123
		modifyingLock = false;
124
	}
125
126
	
127
	/**
128
	 * This class represents the state of the last completion process.
129
	 * Each time the user moves to a new position and calls this action
130
	 * an instance of this inner classs is created  and saved in
131
	 * {@link #lastCompletion}.
132
	 */
133
	private static class CompletionState {
134
				
135
		/** The list of suggestions that was computed when the completion
136
		 * action was first invoked 
137
		 */
138
		final String[] suggestions;
139
				
140
		/** The caret position at which we insert the suggestions */
141
		final int startOffset;
142
				
143
		/** The length of the last suggestion string */
144
		int length;
145
		
146
		/** The index of next suggestion (index into the suggestion array) */
147
		int nextSuggestion;
148
		
149
		CompletionState(String[] suggestions, int startOffset) {
150
			this.suggestions = suggestions;
151
			this.startOffset = startOffset;
152
			length = 0;
153
			nextSuggestion = 0;
154
		}
155
		
156
		public void advance() {
157
			length = suggestions[nextSuggestion].length();
158
			nextSuggestion = (nextSuggestion + 1) % suggestions.length;	
159
		}
160
	}
161
162
	/**
163
	 * @param bundle the resource bundle
164
	 * @param prefix a prefix to be prepended to the various resource keys
165
	 *   (described in <code>ResourceAction</code> constructor), or 
166
	 *   <code>null</code> if none
167
	 * @param editor the text editor
168
	 */
169
	protected EmacsCompleteAction(ResourceBundle bundle, String prefix, ITextEditor editor) {
170
		super(bundle, prefix, editor);
171
	}
172
173
	/* (non-Javadoc)
174
	 * @see org.eclipse.jface.action.Action#run()
175
	 */
176
	public void run() {
177
		if (lastCompletion != null) 
178
		{
179
			completeNext();
180
			return;
181
		} 
182
		String prefix = getCurrentPrefix();
183
		if (prefix == null) {
184
			getTextEditor().getSite().getShell().getDisplay().beep();
185
			return;
186
		}
187
		String[] suggestions = getSuggestions(prefix);
188
		
189
		// if it is single empty suggestion
190
		if (suggestions.length == 1) {
191
			getTextEditor().getSite().getShell().getDisplay().beep();
192
			return;
193
		}
194
		lastCompletion = new CompletionState(suggestions, 
195
				((ITextSelection) getTextEditor().getSelectionProvider().getSelection()).getOffset());
196
		completeNext();
197
	}
198
	
199
	
200
	/**
201
	 * Create the array of suggestions. It scan all open text editors
202
	 * and prefers suggestion from the currently open editor.
203
	 * It also addes the empty suggestion at the end.
204
     * 
205
     * @param currEditor The current editor
206
	 */
207
	public String[] getSuggestions(String prefix) {
208
		
209
		// Change the order of open editors, to make the active editor
210
		// to appear first.
211
		IEditorReference editorsArray[] = getTextEditor().getSite().getWorkbenchWindow().getActivePage().getEditorReferences();
212
		ArrayList editorsVector = new ArrayList();
213
		for (int i = 0; i < editorsArray.length; i++) {
214
			IEditorPart realEditor = editorsArray[i].getEditor(false);
215
			if (realEditor != null) {
216
				editorsVector.add(realEditor);
217
			}
218
		}
219
		editorsVector.remove(getTextEditor());
220
		editorsVector.add(0, getTextEditor());
221
		
222
		// collect the suggestions from all open text editors
223
		LinkedList suggestions = new LinkedList();
224
		for (int i = 0; i < editorsVector.size(); i++) {
225
			if (editorsVector.get(i) instanceof ITextEditor) {
226
				ITextEditor textEditor = (ITextEditor) editorsVector.get(i);
227
				IEditorInput input = textEditor.getEditorInput();
228
				IDocument doc = textEditor.getDocumentProvider().getDocument(input);
229
                
230
				try {
231
					suggestions.addAll(getForwardSuggestions(doc, prefix));
232
				} catch (BadLocationException e) {
233
					// TODO Auto-generated catch block
234
					e.printStackTrace();
235
				}
236
			}
237
		}
238
		
239
		makeUnique(suggestions);
240
				
241
		suggestions.add(""); // empty suggestion
242
		return (String[])suggestions.toArray(new String[0]);
243
	}
244
	
245
	
246
	/**
247
	 * Remove duplicate suggestions, leaving the closest to list head. 
248
	 * @param suggestions
249
	 */
250
	private void makeUnique(LinkedList suggestions) {
251
		HashSet seenAlready = new HashSet();
252
		
253
		for (Iterator i = suggestions.iterator(); i.hasNext();) {
254
			String suggestion = (String) i.next();
255
			if (seenAlready.contains(suggestion)) {
256
				i.remove();
257
			} else {
258
				seenAlready.add(suggestion);
259
			}
260
		}
261
	}
262
263
	/**
264
	 * Copied from {@link FindReplaceDocumentAdapter}.
265
	 * 
266
	 * Converts a non-regex string to a pattern
267
	 * that can be used with the regex search engine.
268
	 * 
269
	 * @param string the non-regex pattern
270
	 * @return the string converted to a regex pattern
271
	 */
272
	private static String asRegPattern(String string) {
273
		StringBuffer out= new StringBuffer(string.length());
274
		boolean quoting= false;
275
		
276
		for (int i= 0, length= string.length(); i < length; i++) {
277
			char ch= string.charAt(i);
278
			if (ch == '\\') {
279
				if (quoting) {
280
					out.append("\\E"); //$NON-NLS-1$
281
					quoting= false;
282
				}
283
				out.append("\\\\"); //$NON-NLS-1$
284
				continue;								
285
			}
286
			if (!quoting) {
287
				out.append("\\Q"); //$NON-NLS-1$
288
				quoting= true;
289
			}
290
			out.append(ch);
291
		}
292
		if (quoting)
293
			out.append("\\E"); //$NON-NLS-1$
294
		
295
		return out.toString();
296
	}
297
298
	
299
	/**
300
	 * Return the list of completion suggestions the correspond to the provided
301
	 * prefix
302
	 * 
303
	 * @param document
304
	 * @param prefix
305
	 * @return
306
	 * @throws BadLocationException
307
	 */
308
	private static ArrayList getForwardSuggestions(IDocument document, String prefix) throws BadLocationException {
309
		ArrayList res = new ArrayList();
310
	
311
		FindReplaceDocumentAdapter searcher = new 
312
			FindReplaceDocumentAdapter(document);
313
		
314
		// search only at word boundaries 
315
		String searchPattern = "\\b" + asRegPattern(prefix);
316
		
317
		IRegion reg = searcher.find(0, searchPattern, true, true, false, true);
318
		while (reg != null) {
319
			// try to complete to a word
320
			IRegion word = searcher.find(reg.getOffset(), "\\w+", true, true, false, true);
321
			if (word.getLength() > reg.getLength() ) { // empty suggestion will be added later
322
				String found = document.get(word.getOffset(), word.getLength());
323
				res.add(found.substring(prefix.length()));
324
			}
325
			int nextPos = word.getOffset() + word.getLength();
326
			if (nextPos >= document.getLength() ) {
327
				break;
328
			}
329
			reg = searcher.find(nextPos, searchPattern, true, true, false, true);
330
		}
331
				
332
		return res;
333
	}
334
	
335
	/**
336
	 * Return the part of a word before the caret.
337
	 * If the caret is not at a middle/end of a word, 
338
	 * returns null.
339
	 */
340
	public String getCurrentPrefix() {
341
		ITextSelection selection = (ITextSelection) getTextEditor().getSelectionProvider().getSelection();
342
		if (selection.getLength() > 0) {
343
			return null;
344
		}
345
		int pos = selection.getOffset();
346
        String docText = doc.get();
347
        int prevNonAlpha = pos;
348
        while (prevNonAlpha > 0 && 
349
        	    Character.isJavaIdentifierPart(docText.charAt(prevNonAlpha-1))) 
350
        {
351
        	prevNonAlpha--;
352
        }
353
        if (prevNonAlpha != pos) {
354
            return docText.substring(prevNonAlpha, pos);
355
        } else {
356
        	return null;
357
        }
358
359
	}
360
}

Return to bug 11668