comparison dwtx/jface/text/contentassist/CompletionProposalPopup.d @ 129:eb30df5ca28b

Added JFace Text sources
author Frank Benoit <benoit@tionex.de>
date Sat, 23 Aug 2008 19:10:48 +0200
parents
children c4fb132a086c
comparison
equal deleted inserted replaced
128:8df1d4193877 129:eb30df5ca28b
1 /*******************************************************************************
2 * Copyright (c) 2000, 2008 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 * IBM Corporation - initial API and implementation
10 * Sean Montgomery, sean_montgomery@comcast.net - https://bugs.eclipse.org/bugs/show_bug.cgi?id=116454
11 * Port to the D programming language:
12 * Frank Benoit <benoit@tionex.de>
13 *******************************************************************************/
14 module dwtx.jface.text.contentassist.CompletionProposalPopup;
15
16 import dwt.dwthelper.utils;
17
18 import java.util.ArrayList;
19 import java.util.List;
20
21 import dwt.DWT;
22 import dwt.custom.BusyIndicator;
23 import dwt.custom.StyleRange;
24 import dwt.events.ControlEvent;
25 import dwt.events.ControlListener;
26 import dwt.events.DisposeEvent;
27 import dwt.events.DisposeListener;
28 import dwt.events.FocusEvent;
29 import dwt.events.FocusListener;
30 import dwt.events.KeyAdapter;
31 import dwt.events.KeyEvent;
32 import dwt.events.KeyListener;
33 import dwt.events.MouseAdapter;
34 import dwt.events.MouseEvent;
35 import dwt.events.SelectionEvent;
36 import dwt.events.SelectionListener;
37 import dwt.events.TraverseEvent;
38 import dwt.events.TraverseListener;
39 import dwt.events.VerifyEvent;
40 import dwt.graphics.Color;
41 import dwt.graphics.Font;
42 import dwt.graphics.FontData;
43 import dwt.graphics.Image;
44 import dwt.graphics.Point;
45 import dwt.graphics.Rectangle;
46 import dwt.layout.GridData;
47 import dwt.layout.GridLayout;
48 import dwt.widgets.Control;
49 import dwt.widgets.Display;
50 import dwt.widgets.Event;
51 import dwt.widgets.Label;
52 import dwt.widgets.Listener;
53 import dwt.widgets.Shell;
54 import dwt.widgets.Table;
55 import dwt.widgets.TableItem;
56 import dwtx.core.commands.AbstractHandler;
57 import dwtx.core.commands.ExecutionEvent;
58 import dwtx.core.commands.ExecutionException;
59 import dwtx.core.commands.IHandler;
60 import dwtx.core.runtime.Assert;
61 import dwtx.jface.bindings.keys.KeySequence;
62 import dwtx.jface.bindings.keys.SWTKeySupport;
63 import dwtx.jface.contentassist.IContentAssistSubjectControl;
64 import dwtx.jface.internal.text.InformationControlReplacer;
65 import dwtx.jface.internal.text.TableOwnerDrawSupport;
66 import dwtx.jface.preference.JFacePreferences;
67 import dwtx.jface.resource.JFaceResources;
68 import dwtx.jface.text.AbstractInformationControlManager;
69 import dwtx.jface.text.BadLocationException;
70 import dwtx.jface.text.DocumentEvent;
71 import dwtx.jface.text.IDocument;
72 import dwtx.jface.text.IDocumentListener;
73 import dwtx.jface.text.IEditingSupport;
74 import dwtx.jface.text.IEditingSupportRegistry;
75 import dwtx.jface.text.IInformationControl;
76 import dwtx.jface.text.IRegion;
77 import dwtx.jface.text.IRewriteTarget;
78 import dwtx.jface.text.ITextViewer;
79 import dwtx.jface.text.ITextViewerExtension;
80 import dwtx.jface.text.TextUtilities;
81 import dwtx.jface.text.AbstractInformationControlManager.Anchor;
82 import dwtx.jface.util.Geometry;
83 import dwtx.jface.viewers.StyledString;
84
85
86 /**
87 * This class is used to present proposals to the user. If additional
88 * information exists for a proposal, then selecting that proposal
89 * will result in the information being displayed in a secondary
90 * window.
91 *
92 * @see dwtx.jface.text.contentassist.ICompletionProposal
93 * @see dwtx.jface.text.contentassist.AdditionalInfoController
94 */
95 class CompletionProposalPopup : IContentAssistListener {
96 /**
97 * Set to <code>true</code> to use a Table with DWT.VIRTUAL.
98 * XXX: This is a workaround for: https://bugs.eclipse.org/bugs/show_bug.cgi?id=90321
99 * More details see also: https://bugs.eclipse.org/bugs/show_bug.cgi?id=98585#c36
100 * @since 3.1
101 */
102 private static final bool USE_VIRTUAL= !"motif".equals(DWT.getPlatform()); //$NON-NLS-1$
103
104
105 /**
106 * Completion proposal selection handler.
107 *
108 * @since 3.4
109 */
110 final class ProposalSelectionHandler : AbstractHandler {
111
112 /**
113 * Selection operation codes.
114 */
115 static final int SELECT_NEXT= 1;
116 static final int SELECT_PREVIOUS= 2;
117
118
119 private int fOperationCode;
120
121 /**
122 * Creates a new selection handler.
123 *
124 * @param operationCode the operation code
125 * @since 3.4
126 */
127 public ProposalSelectionHandler(int operationCode) {
128 Assert.isLegal(operationCode is SELECT_NEXT || operationCode is SELECT_PREVIOUS);
129 fOperationCode= operationCode;
130 }
131
132 /*
133 * @see dwtx.core.commands.AbstractHandler#execute(dwtx.core.commands.ExecutionEvent)
134 * @since 3.4
135 */
136 public Object execute(ExecutionEvent event) throws ExecutionException {
137 int itemCount= fProposalTable.getItemCount();
138 int selectionIndex= fProposalTable.getSelectionIndex();
139 switch (fOperationCode) {
140 case SELECT_NEXT:
141 selectionIndex+= 1;
142 if (selectionIndex > itemCount - 1)
143 selectionIndex= 0;
144 break;
145 case SELECT_PREVIOUS:
146 selectionIndex-= 1;
147 if (selectionIndex < 0)
148 selectionIndex= itemCount - 1;
149 break;
150 }
151 selectProposal(selectionIndex, false);
152 return null;
153 }
154
155 }
156
157
158 /**
159 * The empty proposal displayed if there is nothing else to show.
160 *
161 * @since 3.2
162 */
163 private static final class EmptyProposal : ICompletionProposal, ICompletionProposalExtension {
164
165 String fDisplayString;
166 int fOffset;
167 /*
168 * @see ICompletionProposal#apply(IDocument)
169 */
170 public void apply(IDocument document) {
171 }
172
173 /*
174 * @see ICompletionProposal#getSelection(IDocument)
175 */
176 public Point getSelection(IDocument document) {
177 return new Point(fOffset, 0);
178 }
179
180 /*
181 * @see ICompletionProposal#getContextInformation()
182 */
183 public IContextInformation getContextInformation() {
184 return null;
185 }
186
187 /*
188 * @see ICompletionProposal#getImage()
189 */
190 public Image getImage() {
191 return null;
192 }
193
194 /*
195 * @see ICompletionProposal#getDisplayString()
196 */
197 public String getDisplayString() {
198 return fDisplayString;
199 }
200
201 /*
202 * @see ICompletionProposal#getAdditionalProposalInfo()
203 */
204 public String getAdditionalProposalInfo() {
205 return null;
206 }
207
208 /*
209 * @see dwtx.jface.text.contentassist.ICompletionProposalExtension#apply(dwtx.jface.text.IDocument, char, int)
210 */
211 public void apply(IDocument document, char trigger, int offset) {
212 }
213
214 /*
215 * @see dwtx.jface.text.contentassist.ICompletionProposalExtension#isValidFor(dwtx.jface.text.IDocument, int)
216 */
217 public bool isValidFor(IDocument document, int offset) {
218 return false;
219 }
220
221 /*
222 * @see dwtx.jface.text.contentassist.ICompletionProposalExtension#getTriggerCharacters()
223 */
224 public char[] getTriggerCharacters() {
225 return null;
226 }
227
228 /*
229 * @see dwtx.jface.text.contentassist.ICompletionProposalExtension#getContextInformationPosition()
230 */
231 public int getContextInformationPosition() {
232 return -1;
233 }
234 }
235
236 private final class ProposalSelectionListener : KeyListener {
237 public void keyPressed(KeyEvent e) {
238 if (!Helper.okToUse(fProposalShell))
239 return;
240
241 if (e.character is 0 && e.keyCode is DWT.CTRL) {
242 // http://dev.eclipse.org/bugs/show_bug.cgi?id=34754
243 int index= fProposalTable.getSelectionIndex();
244 if (index >= 0)
245 selectProposal(index, true);
246 }
247 }
248
249 public void keyReleased(KeyEvent e) {
250 if (!Helper.okToUse(fProposalShell))
251 return;
252
253 if (e.character is 0 && e.keyCode is DWT.CTRL) {
254 // http://dev.eclipse.org/bugs/show_bug.cgi?id=34754
255 int index= fProposalTable.getSelectionIndex();
256 if (index >= 0)
257 selectProposal(index, false);
258 }
259 }
260 }
261
262 private final class CommandKeyListener : KeyAdapter {
263 private KeySequence fCommandSequence;
264
265 private CommandKeyListener(KeySequence keySequence) {
266 fCommandSequence= keySequence;
267 }
268
269 public void keyPressed(KeyEvent e) {
270 if (!Helper.okToUse(fProposalShell))
271 return;
272
273 int accelerator= SWTKeySupport.convertEventToUnmodifiedAccelerator(e);
274 KeySequence sequence= KeySequence.getInstance(SWTKeySupport.convertAcceleratorToKeyStroke(accelerator));
275 if (sequence.equals(fCommandSequence))
276 if (fContentAssistant.isPrefixCompletionEnabled())
277 incrementalComplete();
278 else
279 showProposals(false);
280
281 }
282 }
283
284
285 /** The associated text viewer. */
286 private ITextViewer fViewer;
287 /** The associated content assistant. */
288 private ContentAssistant fContentAssistant;
289 /** The used additional info controller. */
290 private AdditionalInfoController fAdditionalInfoController;
291 /** The closing strategy for this completion proposal popup. */
292 private PopupCloser fPopupCloser= new PopupCloser();
293 /** The popup shell. */
294 private Shell fProposalShell;
295 /** The proposal table. */
296 private Table fProposalTable;
297 /** Indicates whether a completion proposal is being inserted. */
298 private bool fInserting= false;
299 /** The key listener to control navigation. */
300 private ProposalSelectionListener fKeyListener;
301 /** List of document events used for filtering proposals. */
302 private List fDocumentEvents= new ArrayList();
303 /** Listener filling the document event queue. */
304 private IDocumentListener fDocumentListener;
305 /** The filter list of proposals. */
306 private ICompletionProposal[] fFilteredProposals;
307 /** The computed list of proposals. */
308 private ICompletionProposal[] fComputedProposals;
309 /** The offset for which the proposals have been computed. */
310 private int fInvocationOffset;
311 /** The offset for which the computed proposals have been filtered. */
312 private int fFilterOffset;
313 /**
314 * The most recently selected proposal.
315 * @since 3.0
316 */
317 private ICompletionProposal fLastProposal;
318 /**
319 * The content assist subject control.
320 * This replaces <code>fViewer</code>
321 *
322 * @since 3.0
323 */
324 private IContentAssistSubjectControl fContentAssistSubjectControl;
325 /**
326 * The content assist subject control adapter.
327 * This replaces <code>fViewer</code>
328 *
329 * @since 3.0
330 */
331 private ContentAssistSubjectControlAdapter fContentAssistSubjectControlAdapter;
332 /**
333 * Remembers the size for this completion proposal popup.
334 * @since 3.0
335 */
336 private Point fSize;
337 /**
338 * Editor helper that communicates that the completion proposal popup may
339 * have focus while the 'logical focus' is still with the editor.
340 * @since 3.1
341 */
342 private IEditingSupport fFocusHelper;
343 /**
344 * Set to true by {@link #computeFilteredProposals(int, DocumentEvent)} if
345 * the returned proposals are a subset of {@link #fFilteredProposals},
346 * <code>false</code> if not.
347 * @since 3.1
348 */
349 private bool fIsFilteredSubset;
350 /**
351 * The filter runnable.
352 *
353 * @since 3.1.1
354 */
355 private final Runnable fFilterRunnable= new Runnable() {
356 public void run() {
357 if (!fIsFilterPending)
358 return;
359
360 fIsFilterPending= false;
361
362 if (!Helper.okToUse(fContentAssistSubjectControlAdapter.getControl()))
363 return;
364
365 int offset= fContentAssistSubjectControlAdapter.getSelectedRange().x;
366 ICompletionProposal[] proposals= null;
367 try {
368 if (offset > -1) {
369 DocumentEvent event= TextUtilities.mergeProcessedDocumentEvents(fDocumentEvents);
370 proposals= computeFilteredProposals(offset, event);
371 }
372 } catch (BadLocationException x) {
373 } finally {
374 fDocumentEvents.clear();
375 }
376 fFilterOffset= offset;
377
378 if (proposals !is null && proposals.length > 0)
379 setProposals(proposals, fIsFilteredSubset);
380 else
381 hide();
382 }
383 };
384 /**
385 * <code>true</code> if <code>fFilterRunnable</code> has been
386 * posted, <code>false</code> if not.
387 *
388 * @since 3.1.1
389 */
390 private bool fIsFilterPending= false;
391 /**
392 * The info message at the bottom of the popup, or <code>null</code> for no popup (if
393 * ContentAssistant does not provide one).
394 *
395 * @since 3.2
396 */
397 private Label fMessageText;
398 /**
399 * The font used for <code>fMessageText</code> or null; dispose when done.
400 *
401 * @since 3.2
402 */
403 private Font fMessageTextFont;
404 /**
405 * The most recent completion offset (used to determine repeated invocation)
406 *
407 * @since 3.2
408 */
409 private int fLastCompletionOffset;
410 /**
411 * The (reusable) empty proposal.
412 *
413 * @since 3.2
414 */
415 private final EmptyProposal fEmptyProposal= new EmptyProposal();
416 /**
417 * The text for the empty proposal, or <code>null</code> to use the default text.
418 *
419 * @since 3.2
420 */
421 private String fEmptyMessage= null;
422 /**
423 * Tells whether colored labels support is enabled.
424 * Only valid while the popup is active.
425 *
426 * @since 3.4
427 */
428 private bool fIsColoredLabelsSupportEnabled= false;
429
430
431 /**
432 * Creates a new completion proposal popup for the given elements.
433 *
434 * @param contentAssistant the content assistant feeding this popup
435 * @param viewer the viewer on top of which this popup appears
436 * @param infoController the information control collaborating with this popup
437 * @since 2.0
438 */
439 public CompletionProposalPopup(ContentAssistant contentAssistant, ITextViewer viewer, AdditionalInfoController infoController) {
440 fContentAssistant= contentAssistant;
441 fViewer= viewer;
442 fAdditionalInfoController= infoController;
443 fContentAssistSubjectControlAdapter= new ContentAssistSubjectControlAdapter(fViewer);
444 }
445
446 /**
447 * Creates a new completion proposal popup for the given elements.
448 *
449 * @param contentAssistant the content assistant feeding this popup
450 * @param contentAssistSubjectControl the content assist subject control on top of which this popup appears
451 * @param infoController the information control collaborating with this popup
452 * @since 3.0
453 */
454 public CompletionProposalPopup(ContentAssistant contentAssistant, IContentAssistSubjectControl contentAssistSubjectControl, AdditionalInfoController infoController) {
455 fContentAssistant= contentAssistant;
456 fContentAssistSubjectControl= contentAssistSubjectControl;
457 fAdditionalInfoController= infoController;
458 fContentAssistSubjectControlAdapter= new ContentAssistSubjectControlAdapter(fContentAssistSubjectControl);
459 }
460
461 /**
462 * Computes and presents completion proposals. The flag indicates whether this call has
463 * be made out of an auto activation context.
464 *
465 * @param autoActivated <code>true</code> if auto activation context
466 * @return an error message or <code>null</code> in case of no error
467 */
468 public String showProposals(final bool autoActivated) {
469
470 if (fKeyListener is null)
471 fKeyListener= new ProposalSelectionListener();
472
473 final Control control= fContentAssistSubjectControlAdapter.getControl();
474
475 if (!Helper.okToUse(fProposalShell) && control !is null && !control.isDisposed()) {
476 // add the listener before computing the proposals so we don't move the caret
477 // when the user types fast.
478 fContentAssistSubjectControlAdapter.addKeyListener(fKeyListener);
479
480 BusyIndicator.showWhile(control.getDisplay(), new Runnable() {
481 public void run() {
482
483 fInvocationOffset= fContentAssistSubjectControlAdapter.getSelectedRange().x;
484 fFilterOffset= fInvocationOffset;
485 fLastCompletionOffset= fFilterOffset;
486 fComputedProposals= computeProposals(fInvocationOffset);
487
488 int count= (fComputedProposals is null ? 0 : fComputedProposals.length);
489 if (count is 0 && hideWhenNoProposals(autoActivated))
490 return;
491
492 if (count is 1 && !autoActivated && canAutoInsert(fComputedProposals[0])) {
493 insertProposal(fComputedProposals[0], (char) 0, 0, fInvocationOffset);
494 hide();
495 } else {
496 createProposalSelector();
497 setProposals(fComputedProposals, false);
498 displayProposals();
499 }
500 }
501 });
502 } else {
503 fLastCompletionOffset= fFilterOffset;
504 handleRepeatedInvocation();
505 }
506
507 return getErrorMessage();
508 }
509
510 /**
511 * Hides the popup and returns <code>true</code> if the popup is configured
512 * to never display an empty list. Returns <code>false</code> otherwise.
513 *
514 * @param autoActivated whether the invocation was auto-activated
515 * @return <code>false</code> if an empty list should be displayed, <code>true</code> otherwise
516 * @since 3.2
517 */
518 private bool hideWhenNoProposals(bool autoActivated) {
519 if (autoActivated || !fContentAssistant.isShowEmptyList()) {
520 if (!autoActivated) {
521 Control control= fContentAssistSubjectControlAdapter.getControl();
522 if (control !is null && !control.isDisposed())
523 control.getDisplay().beep();
524 }
525 hide();
526 return true;
527 }
528 return false;
529 }
530
531 /**
532 * If content assist is set up to handle cycling, then the proposals are recomputed. Otherwise,
533 * nothing happens.
534 *
535 * @since 3.2
536 */
537 private void handleRepeatedInvocation() {
538 if (fContentAssistant.isRepeatedInvocationMode()) {
539 fComputedProposals= computeProposals(fFilterOffset);
540 setProposals(fComputedProposals, false);
541 }
542 }
543
544 /**
545 * Returns the completion proposal available at the given offset of the
546 * viewer's document. Delegates the work to the content assistant.
547 *
548 * @param offset the offset
549 * @return the completion proposals available at this offset
550 */
551 private ICompletionProposal[] computeProposals(int offset) {
552 if (fContentAssistSubjectControl !is null)
553 return fContentAssistant.computeCompletionProposals(fContentAssistSubjectControl, offset);
554 return fContentAssistant.computeCompletionProposals(fViewer, offset);
555 }
556
557 /**
558 * Returns the error message.
559 *
560 * @return the error message
561 */
562 private String getErrorMessage() {
563 return fContentAssistant.getErrorMessage();
564 }
565
566 /**
567 * Creates the proposal selector.
568 */
569 private void createProposalSelector() {
570 if (Helper.okToUse(fProposalShell))
571 return;
572
573 Control control= fContentAssistSubjectControlAdapter.getControl();
574 fProposalShell= new Shell(control.getShell(), DWT.ON_TOP | DWT.RESIZE );
575 fProposalShell.setFont(JFaceResources.getDefaultFont());
576 if (USE_VIRTUAL) {
577 fProposalTable= new Table(fProposalShell, DWT.H_SCROLL | DWT.V_SCROLL | DWT.VIRTUAL);
578
579 Listener listener= new Listener() {
580 public void handleEvent(Event event) {
581 handleSetData(event);
582 }
583 };
584 fProposalTable.addListener(DWT.SetData, listener);
585 } else {
586 fProposalTable= new Table(fProposalShell, DWT.H_SCROLL | DWT.V_SCROLL);
587 }
588
589 fIsColoredLabelsSupportEnabled= fContentAssistant.isColoredLabelsSupportEnabled();
590 if (fIsColoredLabelsSupportEnabled)
591 TableOwnerDrawSupport.install(fProposalTable);
592
593 fProposalTable.setLocation(0, 0);
594 if (fAdditionalInfoController !is null)
595 fAdditionalInfoController.setSizeConstraints(50, 10, true, true);
596
597 GridLayout layout= new GridLayout();
598 layout.marginWidth= 0;
599 layout.marginHeight= 0;
600 layout.verticalSpacing= 1;
601 fProposalShell.setLayout(layout);
602
603 if (fContentAssistant.isStatusLineVisible()) {
604 createMessageText();
605 }
606
607 GridData data= new GridData(GridData.FILL_BOTH);
608
609 Point size= fContentAssistant.restoreCompletionProposalPopupSize();
610 if (size !is null) {
611 fProposalTable.setLayoutData(data);
612 fProposalShell.setSize(size);
613 } else {
614 int height= fProposalTable.getItemHeight() * 10;
615 // use golden ratio as default aspect ratio
616 final double aspectRatio= (1 + Math.sqrt(5)) / 2;
617 int width= (int) (height * aspectRatio);
618 Rectangle trim= fProposalTable.computeTrim(0, 0, width, height);
619 data.heightHint= trim.height;
620 data.widthHint= trim.width;
621 fProposalTable.setLayoutData(data);
622 fProposalShell.pack();
623 }
624 fContentAssistant.addToLayout(this, fProposalShell, ContentAssistant.LayoutManager.LAYOUT_PROPOSAL_SELECTOR, fContentAssistant.getSelectionOffset());
625
626 fProposalShell.addControlListener(new ControlListener() {
627
628 public void controlMoved(ControlEvent e) {}
629
630 public void controlResized(ControlEvent e) {
631 if (fAdditionalInfoController !is null) {
632 // reset the cached resize constraints
633 fAdditionalInfoController.setSizeConstraints(50, 10, true, false);
634 }
635
636 fSize= fProposalShell.getSize();
637 }
638 });
639
640 fProposalShell.setBackground(control.getDisplay().getSystemColor(DWT.COLOR_GRAY));
641
642 Color c= getBackgroundColor(control);
643 fProposalTable.setBackground(c);
644
645 c= getForegroundColor(control);
646 fProposalTable.setForeground(c);
647
648 fProposalTable.addSelectionListener(new SelectionListener() {
649
650 public void widgetSelected(SelectionEvent e) {}
651
652 public void widgetDefaultSelected(SelectionEvent e) {
653 insertSelectedProposalWithMask(e.stateMask);
654 }
655 });
656
657 fPopupCloser.install(fContentAssistant, fProposalTable, fAdditionalInfoController);
658
659 fProposalShell.addDisposeListener(new DisposeListener() {
660 public void widgetDisposed(DisposeEvent e) {
661 unregister(); // but don't dispose the shell, since we're being called from its disposal event!
662 }
663 });
664
665 fProposalTable.setHeaderVisible(false);
666
667 addCommandSupport(fProposalTable);
668 }
669
670 /**
671 * Returns the minimal required height for the proposal, may return 0 if the popup has not been
672 * created yet.
673 *
674 * @return the minimal height
675 * @since 3.3
676 */
677 int getMinimalHeight() {
678 int height= 0;
679 if (Helper.okToUse(fProposalTable)) {
680 int items= fProposalTable.getItemHeight() * 10;
681 Rectangle trim= fProposalTable.computeTrim(0, 0, DWT.DEFAULT, items);
682 height= trim.height;
683 }
684 if (Helper.okToUse(fMessageText))
685 height+= fMessageText.getSize().y + 1;
686 return height;
687 }
688
689 /**
690 * Adds command support to the given control.
691 *
692 * @param control the control to watch for focus
693 * @since 3.2
694 */
695 private void addCommandSupport(final Control control) {
696 final KeySequence commandSequence= fContentAssistant.getRepeatedInvocationKeySequence();
697 if (commandSequence !is null && !commandSequence.isEmpty() && fContentAssistant.isRepeatedInvocationMode()) {
698 control.addFocusListener(new FocusListener() {
699 private CommandKeyListener fCommandKeyListener;
700 public void focusGained(FocusEvent e) {
701 if (Helper.okToUse(control)) {
702 if (fCommandKeyListener is null) {
703 fCommandKeyListener= new CommandKeyListener(commandSequence);
704 fProposalTable.addKeyListener(fCommandKeyListener);
705 }
706 }
707 }
708 public void focusLost(FocusEvent e) {
709 if (fCommandKeyListener !is null) {
710 control.removeKeyListener(fCommandKeyListener);
711 fCommandKeyListener= null;
712 }
713 }
714 });
715 }
716 control.addFocusListener(new FocusListener() {
717 private TraverseListener fTraverseListener;
718 public void focusGained(FocusEvent e) {
719 if (Helper.okToUse(control)) {
720 if (fTraverseListener is null) {
721 fTraverseListener= new TraverseListener() {
722 public void keyTraversed(TraverseEvent event) {
723 if (event.detail is DWT.TRAVERSE_TAB_NEXT) {
724 IInformationControl iControl= fAdditionalInfoController.getCurrentInformationControl2();
725 if (fAdditionalInfoController.getInternalAccessor().canReplace(iControl)) {
726 fAdditionalInfoController.getInternalAccessor().replaceInformationControl(true);
727 event.doit= false;
728 }
729 }
730 }
731 };
732 fProposalTable.addTraverseListener(fTraverseListener);
733 }
734 }
735 }
736 public void focusLost(FocusEvent e) {
737 if (fTraverseListener !is null) {
738 control.removeTraverseListener(fTraverseListener);
739 fTraverseListener= null;
740 }
741 }
742 });
743 }
744
745 /**
746 * Returns the background color to use.
747 *
748 * @param control the control to get the display from
749 * @return the background color
750 * @since 3.2
751 */
752 private Color getBackgroundColor(Control control) {
753 Color c= fContentAssistant.getProposalSelectorBackground();
754 if (c is null)
755 c= JFaceResources.getColorRegistry().get(JFacePreferences.CONTENT_ASSIST_BACKGROUND_COLOR);
756 return c;
757 }
758
759 /**
760 * Returns the foreground color to use.
761 *
762 * @param control the control to get the display from
763 * @return the foreground color
764 * @since 3.2
765 */
766 private Color getForegroundColor(Control control) {
767 Color c= fContentAssistant.getProposalSelectorForeground();
768 if (c is null)
769 c= JFaceResources.getColorRegistry().get(JFacePreferences.CONTENT_ASSIST_FOREGROUND_COLOR);
770 return c;
771 }
772
773 /**
774 * Creates the caption line under the proposal table.
775 *
776 * @since 3.2
777 */
778 private void createMessageText() {
779 if (fMessageText is null) {
780 fMessageText= new Label(fProposalShell, DWT.RIGHT);
781 GridData textData= new GridData(DWT.FILL, DWT.BOTTOM, true, false);
782 fMessageText.setLayoutData(textData);
783 fMessageText.setText(fContentAssistant.getStatusMessage() + " "); //$NON-NLS-1$
784 if (fMessageTextFont is null) {
785 Font font= fMessageText.getFont();
786 Display display= fProposalShell.getDisplay();
787 FontData[] fontDatas= font.getFontData();
788 for (int i= 0; i < fontDatas.length; i++)
789 fontDatas[i].setHeight(fontDatas[i].getHeight() * 9 / 10);
790 fMessageTextFont= new Font(display, fontDatas);
791 }
792 fMessageText.setFont(fMessageTextFont);
793 fMessageText.setBackground(getBackgroundColor(fProposalShell));
794 fMessageText.setForeground(getForegroundColor(fProposalShell));
795
796 if (fContentAssistant.isRepeatedInvocationMode()) {
797 fMessageText.setCursor(fProposalShell.getDisplay().getSystemCursor(DWT.CURSOR_HAND));
798 fMessageText.addMouseListener(new MouseAdapter() {
799 public void mouseUp(MouseEvent e) {
800 fLastCompletionOffset= fFilterOffset;
801 fProposalTable.setFocus();
802 handleRepeatedInvocation();
803 }
804
805 public void mouseDown(MouseEvent e) {
806 }
807 });
808 }
809 }
810 }
811
812 /*
813 * @since 3.1
814 */
815 private void handleSetData(Event event) {
816 TableItem item= (TableItem) event.item;
817 int index= fProposalTable.indexOf(item);
818
819 if (0 <= index && index < fFilteredProposals.length) {
820 ICompletionProposal current= fFilteredProposals[index];
821
822 String displayString;
823 StyleRange[] styleRanges= null;
824 if (fIsColoredLabelsSupportEnabled && current instanceof ICompletionProposalExtension6) {
825 StyledString styledString= ((ICompletionProposalExtension6)current).getStyledDisplayString();
826 displayString= styledString.getString();
827 styleRanges= styledString.getStyleRanges();
828 } else
829 displayString= current.getDisplayString();
830
831 item.setText(displayString);
832 if (fIsColoredLabelsSupportEnabled)
833 TableOwnerDrawSupport.storeStyleRanges(item, 0, styleRanges);
834
835 item.setImage(current.getImage());
836 item.setData(current);
837 } else {
838 // this should not happen, but does on win32
839 }
840 }
841
842 /**
843 * Returns the proposal selected in the proposal selector.
844 *
845 * @return the selected proposal
846 * @since 2.0
847 */
848 private ICompletionProposal getSelectedProposal() {
849 /* Make sure that there is no filter runnable pending.
850 * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=31427
851 */
852 if (fIsFilterPending)
853 fFilterRunnable.run();
854
855 // filter runnable may have hidden the proposals
856 if (!Helper.okToUse(fProposalTable))
857 return null;
858
859 int i= fProposalTable.getSelectionIndex();
860 if (fFilteredProposals is null || i < 0 || i >= fFilteredProposals.length)
861 return null;
862 return fFilteredProposals[i];
863 }
864
865 /**
866 * Takes the selected proposal and applies it.
867 *
868 * @param stateMask the state mask
869 * @since 3.2
870 */
871 private void insertSelectedProposalWithMask(int stateMask) {
872 ICompletionProposal p= getSelectedProposal();
873 hide();
874 if (p !is null)
875 insertProposal(p, (char) 0, stateMask, fContentAssistSubjectControlAdapter.getSelectedRange().x);
876 }
877
878 /**
879 * Applies the given proposal at the given offset. The given character is the
880 * one that triggered the insertion of this proposal.
881 *
882 * @param p the completion proposal
883 * @param trigger the trigger character
884 * @param stateMask the state mask
885 * @param offset the offset
886 * @since 2.1
887 */
888 private void insertProposal(ICompletionProposal p, char trigger, int stateMask, final int offset) {
889
890 fInserting= true;
891 IRewriteTarget target= null;
892 IEditingSupport helper= new IEditingSupport() {
893
894 public bool isOriginator(DocumentEvent event, IRegion focus) {
895 return focus.getOffset() <= offset && focus.getOffset() + focus.getLength() >= offset;
896 }
897
898 public bool ownsFocusShell() {
899 return false;
900 }
901
902 };
903
904 try {
905
906 IDocument document= fContentAssistSubjectControlAdapter.getDocument();
907
908 if (fViewer instanceof ITextViewerExtension) {
909 ITextViewerExtension extension= (ITextViewerExtension) fViewer;
910 target= extension.getRewriteTarget();
911 }
912
913 if (target !is null)
914 target.beginCompoundChange();
915
916 if (fViewer instanceof IEditingSupportRegistry) {
917 IEditingSupportRegistry registry= (IEditingSupportRegistry) fViewer;
918 registry.register(helper);
919 }
920
921
922 if (p instanceof ICompletionProposalExtension2 && fViewer !is null) {
923 ICompletionProposalExtension2 e= (ICompletionProposalExtension2) p;
924 e.apply(fViewer, trigger, stateMask, offset);
925 } else if (p instanceof ICompletionProposalExtension) {
926 ICompletionProposalExtension e= (ICompletionProposalExtension) p;
927 e.apply(document, trigger, offset);
928 } else {
929 p.apply(document);
930 }
931
932 Point selection= p.getSelection(document);
933 if (selection !is null) {
934 fContentAssistSubjectControlAdapter.setSelectedRange(selection.x, selection.y);
935 fContentAssistSubjectControlAdapter.revealRange(selection.x, selection.y);
936 }
937
938 IContextInformation info= p.getContextInformation();
939 if (info !is null) {
940
941 int contextInformationOffset;
942 if (p instanceof ICompletionProposalExtension) {
943 ICompletionProposalExtension e= (ICompletionProposalExtension) p;
944 contextInformationOffset= e.getContextInformationPosition();
945 } else {
946 if (selection is null)
947 selection= fContentAssistSubjectControlAdapter.getSelectedRange();
948 contextInformationOffset= selection.x + selection.y;
949 }
950
951 fContentAssistant.showContextInformation(info, contextInformationOffset);
952 } else
953 fContentAssistant.showContextInformation(null, -1);
954
955
956 } finally {
957 if (target !is null)
958 target.endCompoundChange();
959
960 if (fViewer instanceof IEditingSupportRegistry) {
961 IEditingSupportRegistry registry= (IEditingSupportRegistry) fViewer;
962 registry.unregister(helper);
963 }
964 fInserting= false;
965 }
966 }
967
968 /**
969 * Returns whether this popup has the focus.
970 *
971 * @return <code>true</code> if the popup has the focus
972 */
973 public bool hasFocus() {
974 if (Helper.okToUse(fProposalShell)) {
975 if ((fProposalShell.isFocusControl() || fProposalTable.isFocusControl()))
976 return true;
977 /*
978 * We have to delegate this query to the additional info controller
979 * as well, since the content assistant is the widget token owner
980 * and its closer does not know that the additional info control can
981 * now also take focus.
982 */
983 if (fAdditionalInfoController !is null) {
984 IInformationControl informationControl= fAdditionalInfoController.getCurrentInformationControl2();
985 if (informationControl !is null && informationControl.isFocusControl())
986 return true;
987 InformationControlReplacer replacer= fAdditionalInfoController.getInternalAccessor().getInformationControlReplacer();
988 if (replacer !is null) {
989 informationControl= replacer.getCurrentInformationControl2();
990 if (informationControl !is null && informationControl.isFocusControl())
991 return true;
992 }
993 }
994 }
995
996 return false;
997 }
998
999 /**
1000 * Hides this popup.
1001 */
1002 public void hide() {
1003
1004 unregister();
1005
1006 if (fViewer instanceof IEditingSupportRegistry) {
1007 IEditingSupportRegistry registry= (IEditingSupportRegistry) fViewer;
1008 registry.unregister(fFocusHelper);
1009 }
1010
1011 if (Helper.okToUse(fProposalShell)) {
1012
1013 fContentAssistant.removeContentAssistListener(this, ContentAssistant.PROPOSAL_SELECTOR);
1014
1015 fPopupCloser.uninstall();
1016 fProposalShell.setVisible(false);
1017 fProposalShell.dispose();
1018 fProposalShell= null;
1019 }
1020
1021 if (fMessageTextFont !is null) {
1022 fMessageTextFont.dispose();
1023 fMessageTextFont= null;
1024 }
1025
1026 if (fMessageText !is null) {
1027 fMessageText= null;
1028 }
1029
1030 fEmptyMessage= null;
1031
1032 fLastCompletionOffset= -1;
1033
1034 fContentAssistant.fireSessionEndEvent();
1035 }
1036
1037 /**
1038 * Unregister this completion proposal popup.
1039 *
1040 * @since 3.0
1041 */
1042 private void unregister() {
1043 if (fDocumentListener !is null) {
1044 IDocument document= fContentAssistSubjectControlAdapter.getDocument();
1045 if (document !is null)
1046 document.removeDocumentListener(fDocumentListener);
1047 fDocumentListener= null;
1048 }
1049 fDocumentEvents.clear();
1050
1051 if (fKeyListener !is null && fContentAssistSubjectControlAdapter.getControl() !is null && !fContentAssistSubjectControlAdapter.getControl().isDisposed()) {
1052 fContentAssistSubjectControlAdapter.removeKeyListener(fKeyListener);
1053 fKeyListener= null;
1054 }
1055
1056 if (fLastProposal !is null) {
1057 if (fLastProposal instanceof ICompletionProposalExtension2 && fViewer !is null) {
1058 ICompletionProposalExtension2 extension= (ICompletionProposalExtension2) fLastProposal;
1059 extension.unselected(fViewer);
1060 }
1061 fLastProposal= null;
1062 }
1063
1064 fFilteredProposals= null;
1065 fComputedProposals= null;
1066
1067 fContentAssistant.possibleCompletionsClosed();
1068 }
1069
1070 /**
1071 *Returns whether this popup is active. It is active if the proposal selector is visible.
1072 *
1073 * @return <code>true</code> if this popup is active
1074 */
1075 public bool isActive() {
1076 return fProposalShell !is null && !fProposalShell.isDisposed();
1077 }
1078
1079 /**
1080 * Initializes the proposal selector with these given proposals.
1081 * @param proposals the proposals
1082 * @param isFilteredSubset if <code>true</code>, the proposal table is
1083 * not cleared, but the proposals that are not in the passed array
1084 * are removed from the displayed set
1085 */
1086 private void setProposals(ICompletionProposal[] proposals, bool isFilteredSubset) {
1087 ICompletionProposal[] oldProposals= fFilteredProposals;
1088 ICompletionProposal oldProposal= getSelectedProposal(); // may trigger filtering and a reentrant call to setProposals()
1089 if (oldProposals !is fFilteredProposals) // reentrant call was first - abort
1090 return;
1091
1092 if (Helper.okToUse(fProposalTable)) {
1093 if (oldProposal instanceof ICompletionProposalExtension2 && fViewer !is null)
1094 ((ICompletionProposalExtension2) oldProposal).unselected(fViewer);
1095
1096 if (proposals is null || proposals.length is 0) {
1097 fEmptyProposal.fOffset= fFilterOffset;
1098 fEmptyProposal.fDisplayString= fEmptyMessage !is null ? fEmptyMessage : JFaceTextMessages.getString("CompletionProposalPopup.no_proposals"); //$NON-NLS-1$
1099 proposals= new ICompletionProposal[] { fEmptyProposal };
1100 }
1101
1102 fFilteredProposals= proposals;
1103 final int newLen= proposals.length;
1104 if (USE_VIRTUAL) {
1105 fProposalTable.setItemCount(newLen);
1106 fProposalTable.clearAll();
1107 } else {
1108 fProposalTable.setRedraw(false);
1109 fProposalTable.setItemCount(newLen);
1110 TableItem[] items= fProposalTable.getItems();
1111 for (int i= 0; i < items.length; i++) {
1112 TableItem item= items[i];
1113 ICompletionProposal proposal= proposals[i];
1114 item.setText(proposal.getDisplayString());
1115 item.setImage(proposal.getImage());
1116 item.setData(proposal);
1117 }
1118 fProposalTable.setRedraw(true);
1119 }
1120
1121 Point currentLocation= fProposalShell.getLocation();
1122 Point newLocation= getLocation();
1123 if ((newLocation.x < currentLocation.x && newLocation.y is currentLocation.y) || newLocation.y < currentLocation.y)
1124 fProposalShell.setLocation(newLocation);
1125
1126 selectProposal(0, false);
1127 }
1128 }
1129
1130 /**
1131 * Returns the graphical location at which this popup should be made visible.
1132 *
1133 * @return the location of this popup
1134 */
1135 private Point getLocation() {
1136 int caret= fContentAssistSubjectControlAdapter.getCaretOffset();
1137 Rectangle location= fContentAssistant.getLayoutManager().computeBoundsBelowAbove(fProposalShell, fSize is null ? fProposalShell.getSize() : fSize, caret, this);
1138 return Geometry.getLocation(location);
1139 }
1140
1141 /**
1142 * Returns the size of this completion proposal popup.
1143 *
1144 * @return a Point containing the size
1145 * @since 3.0
1146 */
1147 Point getSize() {
1148 return fSize;
1149 }
1150
1151 /**
1152 * Displays this popup and install the additional info controller, so that additional info
1153 * is displayed when a proposal is selected and additional info is available.
1154 */
1155 private void displayProposals() {
1156
1157 if (!Helper.okToUse(fProposalShell) || !Helper.okToUse(fProposalTable))
1158 return;
1159
1160 if (fContentAssistant.addContentAssistListener(this, ContentAssistant.PROPOSAL_SELECTOR)) {
1161
1162 ensureDocumentListenerInstalled();
1163
1164 if (fFocusHelper is null) {
1165 fFocusHelper= new IEditingSupport() {
1166
1167 public bool isOriginator(DocumentEvent event, IRegion focus) {
1168 return false; // this helper just covers the focus change to the proposal shell, no remote editions
1169 }
1170
1171 public bool ownsFocusShell() {
1172 return true;
1173 }
1174
1175 };
1176 }
1177 if (fViewer instanceof IEditingSupportRegistry) {
1178 IEditingSupportRegistry registry= (IEditingSupportRegistry) fViewer;
1179 registry.register(fFocusHelper);
1180 }
1181
1182
1183 /* https://bugs.eclipse.org/bugs/show_bug.cgi?id=52646
1184 * on GTK, setVisible and such may run the event loop
1185 * (see also https://bugs.eclipse.org/bugs/show_bug.cgi?id=47511)
1186 * Since the user may have already canceled the popup or selected
1187 * an entry (ESC or RETURN), we have to double check whether
1188 * the table is still okToUse. See comments below
1189 */
1190 fProposalShell.setVisible(true); // may run event loop on GTK
1191 // transfer focus since no verify key listener can be attached
1192 if (!fContentAssistSubjectControlAdapter.supportsVerifyKeyListener() && Helper.okToUse(fProposalShell))
1193 fProposalShell.setFocus(); // may run event loop on GTK ??
1194
1195 if (fAdditionalInfoController !is null && Helper.okToUse(fProposalTable)) {
1196 fAdditionalInfoController.install(fProposalTable);
1197 fAdditionalInfoController.handleTableSelectionChanged();
1198 }
1199 } else
1200 hide();
1201 }
1202
1203 /**
1204 * Installs the document listener if not already done.
1205 *
1206 * @since 3.2
1207 */
1208 private void ensureDocumentListenerInstalled() {
1209 if (fDocumentListener is null) {
1210 fDocumentListener= new IDocumentListener() {
1211 public void documentAboutToBeChanged(DocumentEvent event) {
1212 if (!fInserting)
1213 fDocumentEvents.add(event);
1214 }
1215
1216 public void documentChanged(DocumentEvent event) {
1217 if (!fInserting)
1218 filterProposals();
1219 }
1220 };
1221 IDocument document= fContentAssistSubjectControlAdapter.getDocument();
1222 if (document !is null)
1223 document.addDocumentListener(fDocumentListener);
1224 }
1225 }
1226
1227 /*
1228 * @see IContentAssistListener#verifyKey(VerifyEvent)
1229 */
1230 public bool verifyKey(VerifyEvent e) {
1231 if (!Helper.okToUse(fProposalShell))
1232 return true;
1233
1234 char key= e.character;
1235 if (key is 0) {
1236 int newSelection= fProposalTable.getSelectionIndex();
1237 int visibleRows= (fProposalTable.getSize().y / fProposalTable.getItemHeight()) - 1;
1238 int itemCount= fProposalTable.getItemCount();
1239 bool smartToggle= false;
1240 switch (e.keyCode) {
1241
1242 case DWT.ARROW_LEFT :
1243 case DWT.ARROW_RIGHT :
1244 filterProposals();
1245 return true;
1246
1247 case DWT.ARROW_UP :
1248 newSelection -= 1;
1249 if (newSelection < 0)
1250 newSelection= itemCount - 1;
1251 break;
1252
1253 case DWT.ARROW_DOWN :
1254 newSelection += 1;
1255 if (newSelection > itemCount - 1)
1256 newSelection= 0;
1257 break;
1258
1259 case DWT.PAGE_DOWN :
1260 newSelection += visibleRows;
1261 if (newSelection >= itemCount)
1262 newSelection= itemCount - 1;
1263 break;
1264
1265 case DWT.PAGE_UP :
1266 newSelection -= visibleRows;
1267 if (newSelection < 0)
1268 newSelection= 0;
1269 break;
1270
1271 case DWT.HOME :
1272 newSelection= 0;
1273 break;
1274
1275 case DWT.END :
1276 newSelection= itemCount - 1;
1277 break;
1278
1279 default :
1280 if (e.keyCode !is DWT.CAPS_LOCK && e.keyCode !is DWT.MOD1 && e.keyCode !is DWT.MOD2 && e.keyCode !is DWT.MOD3 && e.keyCode !is DWT.MOD4)
1281 hide();
1282 return true;
1283 }
1284
1285 selectProposal(newSelection, smartToggle);
1286
1287 e.doit= false;
1288 return false;
1289
1290 }
1291
1292 // key !is 0
1293 switch (key) {
1294 case 0x1B: // Esc
1295 e.doit= false;
1296 hide();
1297 break;
1298
1299 case '\n': // Ctrl-Enter on w2k
1300 case '\r': // Enter
1301 e.doit= false;
1302 insertSelectedProposalWithMask(e.stateMask);
1303 break;
1304
1305 case '\t':
1306 e.doit= false;
1307 fProposalShell.setFocus();
1308 return false;
1309
1310 default:
1311 ICompletionProposal p= getSelectedProposal();
1312 if (p instanceof ICompletionProposalExtension) {
1313 ICompletionProposalExtension t= (ICompletionProposalExtension) p;
1314 char[] triggers= t.getTriggerCharacters();
1315 if (contains(triggers, key)) {
1316 e.doit= false;
1317 hide();
1318 insertProposal(p, key, e.stateMask, fContentAssistSubjectControlAdapter.getSelectedRange().x);
1319 }
1320 }
1321 }
1322
1323 return true;
1324 }
1325
1326 /**
1327 * Selects the entry with the given index in the proposal selector and feeds
1328 * the selection to the additional info controller.
1329 *
1330 * @param index the index in the list
1331 * @param smartToggle <code>true</code> if the smart toggle key has been pressed
1332 * @since 2.1
1333 */
1334 private void selectProposal(int index, bool smartToggle) {
1335
1336 ICompletionProposal oldProposal= getSelectedProposal();
1337 if (oldProposal instanceof ICompletionProposalExtension2 && fViewer !is null)
1338 ((ICompletionProposalExtension2) oldProposal).unselected(fViewer);
1339
1340 if (fFilteredProposals is null) {
1341 fireSelectionEvent(null, smartToggle);
1342 return;
1343 }
1344
1345 ICompletionProposal proposal= fFilteredProposals[index];
1346 if (proposal instanceof ICompletionProposalExtension2 && fViewer !is null)
1347 ((ICompletionProposalExtension2) proposal).selected(fViewer, smartToggle);
1348
1349 fireSelectionEvent(proposal, smartToggle);
1350
1351 fLastProposal= proposal;
1352
1353 fProposalTable.setSelection(index);
1354 fProposalTable.showSelection();
1355 if (fAdditionalInfoController !is null)
1356 fAdditionalInfoController.handleTableSelectionChanged();
1357 }
1358
1359 /**
1360 * Fires a selection event, see {@link ICompletionListener}.
1361 *
1362 * @param proposal the selected proposal, possibly <code>null</code>
1363 * @param smartToggle true if the smart toggle is on
1364 * @since 3.2
1365 */
1366 private void fireSelectionEvent(ICompletionProposal proposal, bool smartToggle) {
1367 fContentAssistant.fireSelectionEvent(proposal, smartToggle);
1368 }
1369
1370 /**
1371 * Returns whether the given character is contained in the given array of
1372 * characters.
1373 *
1374 * @param characters the list of characters
1375 * @param c the character to look for in the list
1376 * @return <code>true</code> if character belongs to the list
1377 * @since 2.0
1378 */
1379 private bool contains(char[] characters, char c) {
1380
1381 if (characters is null)
1382 return false;
1383
1384 for (int i= 0; i < characters.length; i++) {
1385 if (c is characters[i])
1386 return true;
1387 }
1388
1389 return false;
1390 }
1391
1392 /*
1393 * @see IEventConsumer#processEvent(VerifyEvent)
1394 */
1395 public void processEvent(VerifyEvent e) {
1396 }
1397
1398 /**
1399 * Filters the displayed proposal based on the given cursor position and the
1400 * offset of the original invocation of the content assistant.
1401 */
1402 private void filterProposals() {
1403 if (!fIsFilterPending) {
1404 fIsFilterPending= true;
1405 Control control= fContentAssistSubjectControlAdapter.getControl();
1406 control.getDisplay().asyncExec(fFilterRunnable);
1407 }
1408 }
1409
1410 /**
1411 * Computes the subset of already computed proposals that are still valid for
1412 * the given offset.
1413 *
1414 * @param offset the offset
1415 * @param event the merged document event
1416 * @return the set of filtered proposals
1417 * @since 3.0
1418 */
1419 private ICompletionProposal[] computeFilteredProposals(int offset, DocumentEvent event) {
1420
1421 if (offset is fInvocationOffset && event is null) {
1422 fIsFilteredSubset= false;
1423 return fComputedProposals;
1424 }
1425
1426 if (offset < fInvocationOffset) {
1427 fIsFilteredSubset= false;
1428 fInvocationOffset= offset;
1429 fContentAssistant.fireSessionRestartEvent();
1430 fComputedProposals= computeProposals(fInvocationOffset);
1431 return fComputedProposals;
1432 }
1433
1434 ICompletionProposal[] proposals;
1435 if (offset < fFilterOffset) {
1436 proposals= fComputedProposals;
1437 fIsFilteredSubset= false;
1438 } else {
1439 proposals= fFilteredProposals;
1440 fIsFilteredSubset= true;
1441 }
1442
1443 if (proposals is null) {
1444 fIsFilteredSubset= false;
1445 return null;
1446 }
1447
1448 IDocument document= fContentAssistSubjectControlAdapter.getDocument();
1449 int length= proposals.length;
1450 List filtered= new ArrayList(length);
1451 for (int i= 0; i < length; i++) {
1452
1453 if (proposals[i] instanceof ICompletionProposalExtension2) {
1454
1455 ICompletionProposalExtension2 p= (ICompletionProposalExtension2) proposals[i];
1456 if (p.validate(document, offset, event))
1457 filtered.add(p);
1458
1459 } else if (proposals[i] instanceof ICompletionProposalExtension) {
1460
1461 ICompletionProposalExtension p= (ICompletionProposalExtension) proposals[i];
1462 if (p.isValidFor(document, offset))
1463 filtered.add(p);
1464
1465 } else {
1466 // restore original behavior
1467 fIsFilteredSubset= false;
1468 fInvocationOffset= offset;
1469 fContentAssistant.fireSessionRestartEvent();
1470 fComputedProposals= computeProposals(fInvocationOffset);
1471 return fComputedProposals;
1472 }
1473 }
1474
1475 return (ICompletionProposal[]) filtered.toArray(new ICompletionProposal[filtered.size()]);
1476 }
1477
1478 /**
1479 * Requests the proposal shell to take focus.
1480 *
1481 * @since 3.0
1482 */
1483 public void setFocus() {
1484 if (Helper.okToUse(fProposalShell)) {
1485 fProposalShell.setFocus();
1486 }
1487 }
1488
1489 /**
1490 * Returns <code>true</code> if <code>proposal</code> should be auto-inserted,
1491 * <code>false</code> otherwise.
1492 *
1493 * @param proposal the single proposal that might be automatically inserted
1494 * @return <code>true</code> if <code>proposal</code> can be inserted automatically,
1495 * <code>false</code> otherwise
1496 * @since 3.1
1497 */
1498 private bool canAutoInsert(ICompletionProposal proposal) {
1499 if (fContentAssistant.isAutoInserting()) {
1500 if (proposal instanceof ICompletionProposalExtension4) {
1501 ICompletionProposalExtension4 ext= (ICompletionProposalExtension4) proposal;
1502 return ext.isAutoInsertable();
1503 }
1504 return true; // default behavior before ICompletionProposalExtension4 was introduced
1505 }
1506 return false;
1507 }
1508
1509 /**
1510 * Completes the common prefix of all proposals directly in the code. If no
1511 * common prefix can be found, the proposal popup is shown.
1512 *
1513 * @return an error message if completion failed.
1514 * @since 3.0
1515 */
1516 public String incrementalComplete() {
1517 if (Helper.okToUse(fProposalShell) && fFilteredProposals !is null) {
1518 if (fLastCompletionOffset is fFilterOffset) {
1519 handleRepeatedInvocation();
1520 } else {
1521 fLastCompletionOffset= fFilterOffset;
1522 completeCommonPrefix();
1523 }
1524 } else {
1525 final Control control= fContentAssistSubjectControlAdapter.getControl();
1526
1527 if (fKeyListener is null)
1528 fKeyListener= new ProposalSelectionListener();
1529
1530 if (!Helper.okToUse(fProposalShell) && !control.isDisposed())
1531 fContentAssistSubjectControlAdapter.addKeyListener(fKeyListener);
1532
1533 BusyIndicator.showWhile(control.getDisplay(), new Runnable() {
1534 public void run() {
1535
1536 fInvocationOffset= fContentAssistSubjectControlAdapter.getSelectedRange().x;
1537 fFilterOffset= fInvocationOffset;
1538 fLastCompletionOffset= fFilterOffset;
1539 fFilteredProposals= computeProposals(fInvocationOffset);
1540
1541 int count= (fFilteredProposals is null ? 0 : fFilteredProposals.length);
1542 if (count is 0 && hideWhenNoProposals(false))
1543 return;
1544
1545 if (count is 1 && canAutoInsert(fFilteredProposals[0])) {
1546 insertProposal(fFilteredProposals[0], (char) 0, 0, fInvocationOffset);
1547 hide();
1548 } else {
1549 ensureDocumentListenerInstalled();
1550 if (count > 0 && completeCommonPrefix())
1551 hide();
1552 else {
1553 fComputedProposals= fFilteredProposals;
1554 createProposalSelector();
1555 setProposals(fComputedProposals, false);
1556 displayProposals();
1557 }
1558 }
1559 }
1560 });
1561 }
1562 return getErrorMessage();
1563 }
1564
1565 /**
1566 * Acts upon <code>fFilteredProposals</code>: if there is just one valid
1567 * proposal, it is inserted, otherwise, the common prefix of all proposals
1568 * is inserted into the document. If there is no common prefix, nothing
1569 * happens and <code>false</code> is returned.
1570 *
1571 * @return <code>true</code> if a single proposal was inserted and the
1572 * selector can be closed, <code>false</code> otherwise
1573 * @since 3.0
1574 */
1575 private bool completeCommonPrefix() {
1576
1577 // 0: insert single proposals
1578 if (fFilteredProposals.length is 1) {
1579 if (canAutoInsert(fFilteredProposals[0])) {
1580 insertProposal(fFilteredProposals[0], (char) 0, 0, fFilterOffset);
1581 hide();
1582 return true;
1583 }
1584 return false;
1585 }
1586
1587 // 1: extract pre- and postfix from all remaining proposals
1588 IDocument document= fContentAssistSubjectControlAdapter.getDocument();
1589
1590 // contains the common postfix in the case that there are any proposals matching our LHS
1591 StringBuffer rightCasePostfix= null;
1592 List rightCase= new ArrayList();
1593
1594 bool isWrongCaseMatch= false;
1595
1596 // the prefix of all case insensitive matches. This differs from the document
1597 // contents and will be replaced.
1598 CharSequence wrongCasePrefix= null;
1599 int wrongCasePrefixStart= 0;
1600 // contains the common postfix of all case-insensitive matches
1601 StringBuffer wrongCasePostfix= null;
1602 List wrongCase= new ArrayList();
1603
1604 for (int i= 0; i < fFilteredProposals.length; i++) {
1605 ICompletionProposal proposal= fFilteredProposals[i];
1606
1607 if (!(proposal instanceof ICompletionProposalExtension3))
1608 return false;
1609
1610 int start= ((ICompletionProposalExtension3)proposal).getPrefixCompletionStart(fContentAssistSubjectControlAdapter.getDocument(), fFilterOffset);
1611 CharSequence insertion= ((ICompletionProposalExtension3)proposal).getPrefixCompletionText(fContentAssistSubjectControlAdapter.getDocument(), fFilterOffset);
1612 if (insertion is null)
1613 insertion= proposal.getDisplayString();
1614 try {
1615 int prefixLength= fFilterOffset - start;
1616 int relativeCompletionOffset= Math.min(insertion.length(), prefixLength);
1617 String prefix= document.get(start, prefixLength);
1618 if (!isWrongCaseMatch && insertion.toString().startsWith(prefix)) {
1619 isWrongCaseMatch= false;
1620 rightCase.add(proposal);
1621 CharSequence newPostfix= insertion.subSequence(relativeCompletionOffset, insertion.length());
1622 if (rightCasePostfix is null)
1623 rightCasePostfix= new StringBuffer(newPostfix.toString());
1624 else
1625 truncatePostfix(rightCasePostfix, newPostfix);
1626 } else if (i is 0 || isWrongCaseMatch) {
1627 CharSequence newPrefix= insertion.subSequence(0, relativeCompletionOffset);
1628 if (isPrefixCompatible(wrongCasePrefix, wrongCasePrefixStart, newPrefix, start, document)) {
1629 isWrongCaseMatch= true;
1630 wrongCasePrefix= newPrefix;
1631 wrongCasePrefixStart= start;
1632 CharSequence newPostfix= insertion.subSequence(relativeCompletionOffset, insertion.length());
1633 if (wrongCasePostfix is null)
1634 wrongCasePostfix= new StringBuffer(newPostfix.toString());
1635 else
1636 truncatePostfix(wrongCasePostfix, newPostfix);
1637 wrongCase.add(proposal);
1638 } else {
1639 return false;
1640 }
1641 } else
1642 return false;
1643 } catch (BadLocationException e2) {
1644 // bail out silently
1645 return false;
1646 }
1647
1648 if (rightCasePostfix !is null && rightCasePostfix.length() is 0 && rightCase.size() > 1)
1649 return false;
1650 }
1651
1652 // 2: replace single proposals
1653
1654 if (rightCase.size() is 1) {
1655 ICompletionProposal proposal= (ICompletionProposal) rightCase.get(0);
1656 if (canAutoInsert(proposal) && rightCasePostfix.length() > 0) {
1657 insertProposal(proposal, (char) 0, 0, fInvocationOffset);
1658 hide();
1659 return true;
1660 }
1661 return false;
1662 } else if (isWrongCaseMatch && wrongCase.size() is 1) {
1663 ICompletionProposal proposal= (ICompletionProposal) wrongCase.get(0);
1664 if (canAutoInsert(proposal)) {
1665 insertProposal(proposal, (char) 0, 0, fInvocationOffset);
1666 hide();
1667 return true;
1668 }
1669 return false;
1670 }
1671
1672 // 3: replace post- / prefixes
1673
1674 CharSequence prefix;
1675 if (isWrongCaseMatch)
1676 prefix= wrongCasePrefix;
1677 else
1678 prefix= ""; //$NON-NLS-1$
1679
1680 CharSequence postfix;
1681 if (isWrongCaseMatch)
1682 postfix= wrongCasePostfix;
1683 else
1684 postfix= rightCasePostfix;
1685
1686 if (prefix is null || postfix is null)
1687 return false;
1688
1689 try {
1690 // 4: check if parts of the postfix are already in the document
1691 int to= Math.min(document.getLength(), fFilterOffset + postfix.length());
1692 StringBuffer inDocument= new StringBuffer(document.get(fFilterOffset, to - fFilterOffset));
1693 truncatePostfix(inDocument, postfix);
1694
1695 // 5: replace and reveal
1696 document.replace(fFilterOffset - prefix.length(), prefix.length() + inDocument.length(), prefix.toString() + postfix.toString());
1697
1698 fContentAssistSubjectControlAdapter.setSelectedRange(fFilterOffset + postfix.length(), 0);
1699 fContentAssistSubjectControlAdapter.revealRange(fFilterOffset + postfix.length(), 0);
1700 fFilterOffset+= postfix.length();
1701 fLastCompletionOffset= fFilterOffset;
1702
1703 return false;
1704 } catch (BadLocationException e) {
1705 // ignore and return false
1706 return false;
1707 }
1708 }
1709
1710 /*
1711 * @since 3.1
1712 */
1713 private bool isPrefixCompatible(CharSequence oneSequence, int oneOffset, CharSequence twoSequence, int twoOffset, IDocument document) throws BadLocationException {
1714 if (oneSequence is null || twoSequence is null)
1715 return true;
1716
1717 int min= Math.min(oneOffset, twoOffset);
1718 int oneEnd= oneOffset + oneSequence.length();
1719 int twoEnd= twoOffset + twoSequence.length();
1720
1721 String one= document.get(oneOffset, min - oneOffset) + oneSequence + document.get(oneEnd, Math.min(fFilterOffset, fFilterOffset - oneEnd));
1722 String two= document.get(twoOffset, min - twoOffset) + twoSequence + document.get(twoEnd, Math.min(fFilterOffset, fFilterOffset - twoEnd));
1723
1724 return one.equals(two);
1725 }
1726
1727 /**
1728 * Truncates <code>buffer</code> to the common prefix of <code>buffer</code>
1729 * and <code>sequence</code>.
1730 *
1731 * @param buffer the common postfix to truncate
1732 * @param sequence the characters to truncate with
1733 */
1734 private void truncatePostfix(StringBuffer buffer, CharSequence sequence) {
1735 // find common prefix
1736 int min= Math.min(buffer.length(), sequence.length());
1737 for (int c= 0; c < min; c++) {
1738 if (sequence.charAt(c) !is buffer.charAt(c)) {
1739 buffer.delete(c, buffer.length());
1740 return;
1741 }
1742 }
1743
1744 // all equal up to minimum
1745 buffer.delete(min, buffer.length());
1746 }
1747
1748 /**
1749 * Sets the message for the repetition affordance text at the bottom of the proposal. Only has
1750 * an effect if {@link ContentAssistant#isRepeatedInvocationMode()} returns <code>true</code>.
1751 *
1752 * @param message the new caption
1753 * @since 3.2
1754 */
1755 void setMessage(String message) {
1756 Assert.isNotNull(message);
1757 if (isActive() && fMessageText !is null)
1758 fMessageText.setText(message + " "); //$NON-NLS-1$
1759 }
1760
1761 /**
1762 * Sets the text to be displayed if no proposals are available. Only has an effect if
1763 * {@link ContentAssistant#isShowEmptyList()} returns <code>true</code>.
1764 *
1765 * @param message the empty message
1766 * @since 3.2
1767 */
1768 void setEmptyMessage(String message) {
1769 Assert.isNotNull(message);
1770 fEmptyMessage= message;
1771 }
1772
1773 /**
1774 * Enables or disables showing of the caption line. See also {@link #setMessage(String)}.
1775 *
1776 * @param show
1777 * @since 3.2
1778 */
1779 public void setStatusLineVisible(bool show) {
1780 if (!isActive() || show is (fMessageText !is null))
1781 return; // nothing to do
1782
1783 if (show) {
1784 createMessageText();
1785 } else {
1786 fMessageText.dispose();
1787 fMessageText= null;
1788 }
1789 fProposalShell.layout();
1790 }
1791
1792 /**
1793 * Informs the popup that it is being placed above the caret line instead of below.
1794 *
1795 * @param above <code>true</code> if the location of the popup is above the caret line, <code>false</code> if it is below
1796 * @since 3.3
1797 */
1798 void switchedPositionToAbove(bool above) {
1799 if (fAdditionalInfoController !is null) {
1800 fAdditionalInfoController.setFallbackAnchors(new Anchor[] {
1801 AbstractInformationControlManager.ANCHOR_RIGHT,
1802 AbstractInformationControlManager.ANCHOR_LEFT,
1803 above ? AbstractInformationControlManager.ANCHOR_TOP : AbstractInformationControlManager.ANCHOR_BOTTOM
1804 });
1805 }
1806 }
1807
1808 /**
1809 * Returns a new proposal selection handler.
1810 *
1811 * @param operationCode the operation code
1812 * @return the handler
1813 * @since 3.4
1814 */
1815 IHandler createProposalSelectionHandler(int operationCode) {
1816 return new ProposalSelectionHandler(operationCode);
1817 }
1818
1819 }