# HG changeset patch # User Sam Harwell # Date 1330196455 21600 # Node ID 31225ddf6f59da925e771640fe404c211a735b0b # Parent 81cafa2674cba8d7bd3b58301b747fd5b5d316cb Updated completion support diff -r 81cafa2674cb -r 31225ddf6f59 editor.completion/nbproject/project.xml --- a/editor.completion/nbproject/project.xml Sat Feb 25 17:26:54 2012 +0100 +++ b/editor.completion/nbproject/project.xml Sat Feb 25 13:00:55 2012 -0600 @@ -50,6 +50,15 @@ org.netbeans.modules.editor.completion + org.netbeans.api.annotations.common + + + + 1 + 1.11 + + + org.netbeans.modules.editor diff -r 81cafa2674cb -r 31225ddf6f59 editor.completion/src/org/netbeans/modules/editor/completion/CompletionImpl.java --- a/editor.completion/src/org/netbeans/modules/editor/completion/CompletionImpl.java Sat Feb 25 17:26:54 2012 +0100 +++ b/editor.completion/src/org/netbeans/modules/editor/completion/CompletionImpl.java Sat Feb 25 13:00:55 2012 -0600 @@ -49,6 +49,7 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.ref.WeakReference; +import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -129,6 +130,64 @@ private static final int PLEASE_WAIT_TIMEOUT = 750; private static final int PRESCAN = 25; + + /** + * A default {@link CompletionController} which is used for the "Please wait..." + * and "No suggestions" completion results. + */ + private final CompletionController FALLBACK_COMPLETION_CONTROLLER = + new CompletionController() { + @Override + public void sortItems(List items, int sortType) { + assert items.size() == 1; + } + + @Override + public Selection getSelection(List items) { + assert items.size() == 1; + return Selection.DEFAULT; + } + + @Override + public void defaultAction(CompletionItem bestMatch, boolean isSelected) { + if (isSelected) { + bestMatch.defaultAction(getActiveComponent()); + } + } + + @Override + public void processKeyEvent(KeyEvent evt, CompletionItem bestMatch, + boolean isSelected) { + bestMatch.processKeyEvent(evt); + } + + @Override + public void render(Graphics g, Font defaultFont, Color foregroundColor, + Color backgroundColor, Color selectedForegroundColor, + Color selectedBackgroundColor, int width, int height, CompletionItem item, + boolean isBestMatch, boolean isSelected) { + if (isBestMatch) { + // Clear the background + g.setColor(selectedBackgroundColor); + g.fillRect(0, 0, width, height); + g.setColor(selectedForegroundColor); + item.render(g, defaultFont, selectedForegroundColor, + selectedBackgroundColor, width, height, isBestMatch); + } else { + // Clear the background + g.setColor(backgroundColor); + g.fillRect(0, 0, width, height); + g.setColor(foregroundColor); + item.render(g, defaultFont, foregroundColor, backgroundColor, + width, height, isBestMatch); + } + } + + @Override + public boolean instantSubstitution(CompletionItem uniqueMatch) { + return uniqueMatch.instantSubstitution(getActiveComponent()); + } + }; public static CompletionImpl get() { if (singleton == null) @@ -166,8 +225,12 @@ /* Completion providers registered for the active component (its mime-type). Changed in AWT only. */ private CompletionProvider[] activeProviders = null; - /** Mapping of mime-type to array of providers. Changed in AWT only. */ - private HashMap providersCache = new HashMap(); + /** Completion providers registered for the active component (its mime-type). Changed in AWT only. */ + private CompletionControllerProvider[] activeControllerProviders = null; + + /** Mapping of mime-type to service provider type to array of providers. Changed in AWT only. */ + private HashMap, Object[]>> providersCache = + new HashMap, Object[]>>(); /** * Result of the completion query. @@ -253,7 +316,9 @@ docAutoPopupTimer = new Timer(0, new ActionListener() { public void actionPerformed(ActionEvent e) { - if (lastSelectedItem == null || lastSelectedItem.get() != layout.getSelectedCompletionItem()) + SelectedCompletionItem selectedItem = layout.getSelectedCompletionItem(); + CompletionItem currentSelectedItem = selectedItem != null ? selectedItem.getItem() : null; + if (lastSelectedItem == null || lastSelectedItem.get() != currentSelectedItem) showDocumentation(); } }); @@ -278,7 +343,9 @@ } } layout.showCompletion(Collections.singletonList(waitText), - null, -1, CompletionImpl.this, null, null, 0); + null, -1, CompletionImpl.this, null, null, + FALLBACK_COMPLETION_CONTROLLER, + CompletionController.Selection.DEFAULT); pleaseWaitDisplayed = true; if (!politeWaitText) { long when = System.currentTimeMillis() - PLEASE_WAIT_TIMEOUT; @@ -328,7 +395,7 @@ CompletionSettings.getInstance(getActiveComponent()).completionAutoPopup()) { autoModEndOffset = modEndOffset; if (completionResultNull) - showCompletion(false, false, true, CompletionProvider.COMPLETION_QUERY_TYPE); + showCompletion(false, false, true, type & (CompletionProvider.COMPLETION_QUERY_MASK | CompletionProvider.USER_QUERY_MASK)); } boolean tooltipResultNull; @@ -497,45 +564,62 @@ } private void initActiveProviders(JTextComponent component) { - activeProviders = (component != null) - ? getCompletionProvidersForComponent(component, true) + activeProviders = initActiveProviders(CompletionProvider.class, component); + // currently only one async warm up task is allowed at once + activeControllerProviders = null; + } + + private T[] initActiveProviders(Class clazz, JTextComponent component) { + T[] providers = (component != null) + ? getProvidersForComponent(clazz, component, true) : null; + if (LOG.isLoggable(Level.FINE)) { - StringBuffer sb = new StringBuffer("Completion PROVIDERS:\n"); // NOI18N - if (activeProviders != null) { - for (int i = 0; i < activeProviders.length; i++) { + StringBuilder sb = new StringBuilder(clazz.getName() + " PROVIDERS:\n"); // NOI18N + if (providers != null) { + for (int i = 0; i < providers.length; i++) { sb.append("providers["); // NOI18N sb.append(i); sb.append("]: "); // NOI18N - sb.append(activeProviders[i].getClass()); + sb.append(providers[i].getClass()); sb.append('\n'); } } LOG.fine(sb.toString()); } + + return providers; } private boolean ensureActiveProviders() { - if (activeProviders != null) - return true; + activeProviders = ensureActiveProviders(CompletionProvider.class, activeProviders); + activeControllerProviders = ensureActiveProviders(CompletionControllerProvider.class, activeControllerProviders); + // allow activeControllerProviders to be null since we have a + // DefaultCompletionControllerProvider to fall back on. + return activeProviders != null; + } + + private T[] ensureActiveProviders(Class clazz, T[] providers) { + if (providers != null) + return providers; JTextComponent component = getActiveComponent(); - activeProviders = (component != null) - ? getCompletionProvidersForComponent(component, false) + providers = (component != null) + ? getProvidersForComponent(clazz, component, false) : null; if (LOG.isLoggable(Level.FINE)) { - StringBuffer sb = new StringBuffer("Completion PROVIDERS:\n"); // NOI18N - if (activeProviders != null) { - for (int i = 0; i < activeProviders.length; i++) { + StringBuilder sb = new StringBuilder(clazz.getName() + " PROVIDERS:\n"); // NOI18N + if (providers != null) { + for (int i = 0; i < providers.length; i++) { sb.append("providers["); // NOI18N sb.append(i); sb.append("]: "); // NOI18N - sb.append(activeProviders[i].getClass()); + sb.append(providers[i].getClass()); sb.append('\n'); } } LOG.fine(sb.toString()); } - return activeProviders != null; + return providers; } private void restartCompletionAutoPopupTimer() { @@ -553,8 +637,8 @@ docAutoPopupTimer.setInitialDelay(docDelay); docAutoPopupTimer.restart(); } - - private CompletionProvider[] getCompletionProvidersForComponent(JTextComponent component, boolean asyncWarmUp) { + + private T[] getProvidersForComponent(final Class clazz, JTextComponent component, boolean asyncWarmUp) { assert (SwingUtilities.isEventDispatchThread()); if (component == null) @@ -569,15 +653,26 @@ BaseKit kit = Utilities.getKit(component); if (kit == null) { - return new CompletionProvider[0]; + @SuppressWarnings("unchecked") + T[] result = (T[])Array.newInstance(clazz, 0); + return result; } mimeType = kit.getContentType(); } - if (providersCache.containsKey(mimeType)) - return providersCache.get(mimeType); + HashMap, Object[]> providers = providersCache.get(mimeType); + if (providers == null) { + providers = new HashMap, Object[]>(); + providersCache.put(mimeType, providers); + } + if (providers.containsKey(clazz)) { + @SuppressWarnings("unchecked") + T[] result = (T[])providers.get(clazz); + return result; + } + if (asyncWarmUpTask != null) { if (asyncWarmUp && mimeType != null && mimeType.equals(asyncWarmUpMimeType)) return null; @@ -587,24 +682,27 @@ asyncWarmUpTask = null; asyncWarmUpMimeType = null; } + final Lookup lookup = MimeLookup.getLookup(MimePath.get(mimeType)); if (asyncWarmUp) { asyncWarmUpMimeType = mimeType; asyncWarmUpTask = RequestProcessor.getDefault().post(new Runnable() { @Override public void run() { - lookup.lookupAll(CompletionProvider.class); + lookup.lookupAll(clazz); } }); return null; } - Collection col = lookup.lookupAll(CompletionProvider.class); + + Collection col = lookup.lookupAll(clazz); int size = col.size(); - CompletionProvider[] ret = size == 0 ? null : col.toArray(new CompletionProvider[size]); - providersCache.put(mimeType, ret); + @SuppressWarnings("unchecked") + T[] ret = size == 0 ? null : col.toArray((T[])Array.newInstance(clazz, size)); + providers.put(clazz, ret); return ret; } - + private void dispatchKeyEvent(KeyEvent e) { if (e == null) return; @@ -624,21 +722,22 @@ } } if (layout.isCompletionVisible()) { - CompletionItem item = layout.getSelectedCompletionItem(); - if (item != null) { + SelectedCompletionItem item = layout.getSelectedCompletionItem(); + if (item != null && completionResult != null) { sendUndoableEdit(doc, CloneableEditorSupport.BEGIN_COMMIT_GROUP); try { if (compEditable && !guardedPos) { LogRecord r = new LogRecord(Level.FINE, "COMPL_KEY_SELECT"); // NOI18N - r.setParameters(new Object[] {e.getKeyChar(), layout.getSelectedIndex(), item.getClass().getSimpleName()}); - item.processKeyEvent(e); + r.setParameters(new Object[] {e.getKeyChar(), layout.getSelectedIndex(), item.getItem().getClass().getSimpleName()}); + completionResult.getController().processKeyEvent(e, item.getItem(), item.isSelected()); if (e.isConsumed()) { uilog(r); return; } } - // Call default action if ENTER was pressed - if (e.getKeyCode() == KeyEvent.VK_ENTER && e.getID() == KeyEvent.KEY_PRESSED) { + // Call default action if ENTER was pressed and the item is selected + // TODO: move this functionality to the CompletionController + if (item.isSelected() && e.getKeyCode() == KeyEvent.VK_ENTER && e.getID() == KeyEvent.KEY_PRESSED) { e.consume(); if (guardedPos) { Toolkit.getDefaultToolkit().beep(); @@ -648,8 +747,8 @@ consumeIdentifier(); } LogRecord r = new LogRecord(Level.FINE, "COMPL_KEY_SELECT_DEFAULT"); // NOI18N - r.setParameters(new Object[]{'\n', layout.getSelectedIndex(), item.getClass().getSimpleName()}); - item.defaultAction(getActiveComponent()); + r.setParameters(new Object[]{'\n', layout.getSelectedIndex(), item.getItem().getClass().getSimpleName()}); + completionResult.getController().defaultAction(item.getItem(), item.isSelected()); uilog(r); } return; @@ -662,6 +761,7 @@ || e.getKeyCode() == KeyEvent.VK_HOME || e.getKeyCode() == KeyEvent.VK_END) { hideCompletion(false); } + // TODO: move this functionality to the CompletionController if (e.getKeyCode() == KeyEvent.VK_TAB && doc.getProperty(CT_HANDLER_DOC_PROPERTY) == null) { e.consume(); if (guardedPos) { @@ -716,8 +816,12 @@ } } else { completionCancel(); - if (explicitQuery) - layout.showCompletion(Collections.singletonList(NO_SUGGESTIONS), null, -1, CompletionImpl.this, null, null, 0); + if (explicitQuery) { + layout.showCompletion(Collections.singletonList(NO_SUGGESTIONS), + null, -1, CompletionImpl.this, null, null, + FALLBACK_COMPLETION_CONTROLLER, + CompletionController.Selection.DEFAULT); + } pleaseWaitDisplayed = false; stopProfiling(); } @@ -858,9 +962,9 @@ return; } } - CompletionItem item = layout.getSelectedCompletionItem(); + SelectedCompletionItem item = layout.getSelectedCompletionItem(); if (item != null) - item.defaultAction(c); + localCompletionResult.getController().defaultAction(item.getItem(), item.isSelected()); } } @@ -949,7 +1053,7 @@ final ArrayList sortedResultItems = new ArrayList(size = resultItems.size()); if (size > 0) { - Collections.sort(resultItems, CompletionItemComparator.get(getSortType())); + result.getController().sortItems(resultItems, getSortType()); int cnt = 0; for(int i = 0; i < size; i++) { CompletionItem item = resultItems.get(i); @@ -968,11 +1072,11 @@ final boolean noSuggestions = sortedResultItems.size() == 0; if (noSuggestions) { - if (hasAdditionalItems && qType == CompletionProvider.COMPLETION_QUERY_TYPE) { + if (hasAdditionalItems && (qType & CompletionProvider.COMPLETION_ALL_QUERY_TYPE) != CompletionProvider.COMPLETION_ALL_QUERY_TYPE) { showCompletion(this.explicitQuery, this.refreshedQuery, false, CompletionProvider.COMPLETION_ALL_QUERY_TYPE); return; } - if (!explicitQuery) { + if (!explicitQuery) { hideCompletion(false); return; } @@ -992,29 +1096,37 @@ Document doc = c.getDocument(); CompletionSettings cs = CompletionSettings.getInstance(c); int caretOffset = c.getSelectionStart(); - // completionResults = null; - if (sortedResultItems.size() == 1 && !refreshedQuery && explicitQuery + + CompletionController.Selection selection = result.getController().getSelection(sortedResultItems); + // the CompletionController should be returning a valid selection + assert selection != null + && (sortedResultItems.isEmpty() + || selection.getIndex() >= 0 && selection.getIndex() < sortedResultItems.size()); + if (selection == null || selection.getIndex() < 0 || selection.getIndex() > sortedResultItems.size()) { + selection = CompletionController.Selection.DEFAULT; + } + + if (selection.isUnique() && !refreshedQuery && explicitQuery && cs.completionInstantSubstitution() && c.isEditable() && !(doc instanceof GuardedDocument && ((GuardedDocument)doc).isPosGuarded(caretOffset))) { + + sendUndoableEdit(doc, CloneableEditorSupport.BEGIN_COMMIT_GROUP); try { - int[] block = Utilities.getIdentifierBlock(c, caretOffset); - if (block == null || block[1] == caretOffset) { // NOI18N - CompletionItem item = sortedResultItems.get(0); - sendUndoableEdit(doc, CloneableEditorSupport.BEGIN_COMMIT_GROUP); - try { - if (item.instantSubstitution(c)) - return; - } finally { - sendUndoableEdit(doc, CloneableEditorSupport.END_COMMIT_GROUP); - } - } - } catch (BadLocationException ex) { + if (result.getController().instantSubstitution(sortedResultItems.get(selection.getIndex()))) + return; + } finally { + sendUndoableEdit(doc, CloneableEditorSupport.END_COMMIT_GROUP); } } - int selectedIndex = getCompletionPreSelectionIndex(sortedResultItems); c.putClientProperty("completion-visible", Boolean.TRUE); - layout.showCompletion(noSuggestions ? Collections.singletonList(NO_SUGGESTIONS) : sortedResultItems, displayTitle, displayAnchorOffset, CompletionImpl.this, displayAdditionalItems ? hasAdditionalItemsText.toString() : null, displayAdditionalItems ? completionShortcut : null, selectedIndex); + layout.showCompletion( + noSuggestions ? Collections.singletonList(NO_SUGGESTIONS) : sortedResultItems, + displayTitle, displayAnchorOffset, CompletionImpl.this, + displayAdditionalItems ? hasAdditionalItemsText.toString() : null, + displayAdditionalItems ? completionShortcut : null, + result.getController(), + selection); pleaseWaitDisplayed = false; stopProfiling(); @@ -1032,32 +1144,6 @@ }; runInAWT(requestShowRunnable); } - - private int getCompletionPreSelectionIndex(List items) { - String prefix = null; - if(getActiveDocument() instanceof BaseDocument) { - BaseDocument doc = (BaseDocument)getActiveDocument(); - int caretOffset = getActiveComponent().getSelectionStart(); - try { - int[] block = Utilities.getIdentifierBlock(doc, caretOffset); - if (block != null) { - block[1] = caretOffset; - prefix = doc.getText(block); - } - } catch (BadLocationException ble) { - } - } - if (prefix != null && prefix.length() > 0) { - int idx = 0; - for (CompletionItem item : items) { - CharSequence text = item.getInsertPrefix(); - if (text != null && text.toString().startsWith(prefix)) - return idx; - idx++; - } - } - return 0; - } /** * May be called from any thread. The UI changes will be rescheduled into AWT. @@ -1148,10 +1234,10 @@ List documentationResultSets = docResult.getResultSets(); CompletionTask docTask; - CompletionItem selectedItem = layout.getSelectedCompletionItem(); + SelectedCompletionItem selectedItem = layout.getSelectedCompletionItem(); if (selectedItem != null) { - lastSelectedItem = new WeakReference(selectedItem); - docTask = selectedItem.createDocumentationTask(); + lastSelectedItem = new WeakReference(selectedItem.getItem()); + docTask = selectedItem.getItem().createDocumentationTask(); if (docTask != null) { // attempt the documentation for selected item CompletionResultSetImpl resultSet = new CompletionResultSetImpl( this, newDocumentationResult, docTask, CompletionProvider.DOCUMENTATION_QUERY_TYPE); @@ -1270,8 +1356,8 @@ List toolTipResultSets = newToolTipResult.getResultSets(); CompletionTask toolTipTask; - CompletionItem selectedItem = layout.getSelectedCompletionItem(); - if (selectedItem != null && (toolTipTask = selectedItem.createToolTipTask()) != null) { + SelectedCompletionItem selectedItem = layout.getSelectedCompletionItem(); + if (selectedItem != null && (toolTipTask = selectedItem.getItem().createToolTipTask()) != null) { CompletionResultSetImpl resultSet = new CompletionResultSetImpl( this, newToolTipResult, toolTipTask, CompletionProvider.TOOLTIP_QUERY_TYPE); toolTipResultSets.add(resultSet); @@ -1441,7 +1527,7 @@ void finishNotify(CompletionResultSetImpl finishedResult) { Result localResult; boolean finished = false; - switch (finishedResult.getQueryType()) { + switch (finishedResult.getQueryType() & CompletionProvider.RESERVED_QUERY_MASK) { case CompletionProvider.COMPLETION_QUERY_TYPE: case CompletionProvider.COMPLETION_ALL_QUERY_TYPE: synchronized (this) { @@ -1507,7 +1593,7 @@ private static CompletionResultSetImpl findFirstValidResult(List resultSets) { for (int i = 0; i < resultSets.size(); i++) { CompletionResultSetImpl result = resultSets.get(i); - switch (result.getQueryType()) { + switch (result.getQueryType() & CompletionProvider.RESERVED_QUERY_MASK) { case CompletionProvider.DOCUMENTATION_QUERY_TYPE: if (result.getDocumentation() != null) { return result; @@ -1692,6 +1778,8 @@ private boolean invoked; private boolean cancelled; private boolean beforeQuery = true; + + private CompletionController controller; Result(int resultSetsSize) { resultSets = new ArrayList(resultSetsSize); @@ -1706,6 +1794,37 @@ return resultSets; } + CompletionController getController() { + synchronized (resultSets) { + if (controller == null) { + if (resultSets.isEmpty()) { + return FALLBACK_COMPLETION_CONTROLLER; + } + + JTextComponent component = getActiveComponent(); + CompletionTask task = resultSets.get(0).getTask(); + int queryType = resultSets.get(0).getQueryType(); + CompletionControllerProvider[] providers = activeControllerProviders; + if (providers != null) { + for (CompletionControllerProvider provider : providers) { + controller = provider.createController(component, task, queryType); + if (controller != null) { + break; + } + } + } + + if (controller == null) { + CompletionControllerProvider provider = + DefaultCompletionControllerProvider.INSTANCE; + controller = provider.createController(component, task, queryType); + } + } + + return controller; + } + } + /** * Cancel the resultSets. *
diff -r 81cafa2674cb -r 31225ddf6f59 editor.completion/src/org/netbeans/modules/editor/completion/CompletionItemComparator.java --- a/editor.completion/src/org/netbeans/modules/editor/completion/CompletionItemComparator.java Sat Feb 25 17:26:54 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,111 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved. - * - * Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common - * Development and Distribution License("CDDL") (collectively, the - * "License"). You may not use this file except in compliance with the - * License. You can obtain a copy of the License at - * http://www.netbeans.org/cddl-gplv2.html - * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the - * specific language governing permissions and limitations under the - * License. When distributing the software, include this License Header - * Notice in each file and include the License file at - * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the GPL Version 2 section of the License file that - * accompanied this code. If applicable, add the following below the - * License Header, with the fields enclosed by brackets [] replaced by - * your own identifying information: - * "Portions Copyrighted [year] [name of copyright owner]" - * - * Contributor(s): - * - * The Original Software is NetBeans. The Initial Developer of the Original - * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun - * Microsystems, Inc. All Rights Reserved. - * - * If you wish your version of this file to be governed by only the CDDL - * or only the GPL Version 2, indicate your decision by adding - * "[Contributor] elects to include this software in this distribution - * under the [CDDL or GPL Version 2] license." If you do not indicate a - * single choice of license, a recipient has the option to distribute - * your version of this file under either the CDDL, the GPL Version 2 or - * to extend the choice of license to its licensees as provided above. - * However, if you add GPL Version 2 code and therefore, elected the GPL - * Version 2 license, then the option applies only if the new code is - * made subject to such option by the copyright holder. - */ - -package org.netbeans.modules.editor.completion; - -import java.util.Comparator; -import org.netbeans.spi.editor.completion.CompletionItem; -import org.netbeans.spi.editor.completion.CompletionResultSet; - -/** - * Comparator for completion items either by sort priority or by sort text. - * - * @author Dusan Balek, Miloslav Metelka - */ - -public class CompletionItemComparator implements Comparator { - - public static final Comparator BY_PRIORITY = new CompletionItemComparator(true); - - public static final Comparator ALPHABETICAL = new CompletionItemComparator(false); - - private final boolean byPriority; - - private CompletionItemComparator(boolean byPriority) { - this.byPriority = byPriority; - } - - public static final Comparator get(int sortType) { - if (sortType == CompletionResultSet.PRIORITY_SORT_TYPE) - return BY_PRIORITY; - if (sortType == CompletionResultSet.TEXT_SORT_TYPE) - return ALPHABETICAL; - throw new IllegalArgumentException(); - } - - public int compare(CompletionItem i1, CompletionItem i2) { - if (i1 == i2) - return 0; - if (byPriority) { - int importanceDiff = i1.getSortPriority() - i2.getSortPriority(); - if (importanceDiff != 0) - return importanceDiff; - int alphabeticalDiff = compareText(i1.getSortText(), i2.getSortText()); - return alphabeticalDiff; - } else { - int alphabeticalDiff = compareText(i1.getSortText(), i2.getSortText()); - if (alphabeticalDiff != 0) - return alphabeticalDiff; - int importanceDiff = i1.getSortPriority() - i2.getSortPriority(); - return importanceDiff; - } - } - - private static int compareText(CharSequence text1, CharSequence text2) { - if (text1 == null) - text1 = ""; //NOI18N - if (text2 == null) - text2 = ""; //NOI18N - int len = Math.min(text1.length(), text2.length()); - for (int i = 0; i < len; i++) { - char ch1 = text1.charAt(i); - char ch2 = text2.charAt(i); - if (ch1 != ch2) { - return ch1 - ch2; - } - } - return text1.length() - text2.length(); - } - -} diff -r 81cafa2674cb -r 31225ddf6f59 editor.completion/src/org/netbeans/modules/editor/completion/CompletionJList.java --- a/editor.completion/src/org/netbeans/modules/editor/completion/CompletionJList.java Sat Feb 25 17:26:54 2012 +0100 +++ b/editor.completion/src/org/netbeans/modules/editor/completion/CompletionJList.java Sat Feb 25 13:00:55 2012 -0600 @@ -53,8 +53,10 @@ import javax.accessibility.AccessibleContext; import javax.swing.*; import javax.swing.text.JTextComponent; +import org.netbeans.api.annotations.common.NonNull; import org.netbeans.editor.LocaleSupport; +import org.netbeans.spi.editor.completion.CompletionController; import org.netbeans.spi.editor.completion.CompletionItem; import org.netbeans.spi.editor.completion.LazyCompletionItem; @@ -75,6 +77,10 @@ private int maxVisibleRowCount; private JTextComponent editorComponent; private int smartIndex; + /** The current completion controller. */ + private CompletionController controller; + /** true if the best match is selected, otherwise false. */ + private boolean isSelected; public CompletionJList(int maxVisibleRowCount, MouseListener mouseListener, JTextComponent editorComponent) { this.maxVisibleRowCount = maxVisibleRowCount; @@ -91,28 +97,26 @@ setCellRenderer(new ListCellRenderer() { private ListCellRenderer defaultRenderer = new DefaultListCellRenderer(); - public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isBestMatch, boolean cellHasFocus) { if( value instanceof CompletionItem ) { CompletionItem item = (CompletionItem)value; renderComponent.setItem(item); - renderComponent.setSelected(isSelected); + renderComponent.setSelected(isBestMatch, isBestMatch && CompletionJList.this.isSelected); renderComponent.setSeparator(smartIndex > 0 && smartIndex == index); - Color bgColor; - Color fgColor; - if (isSelected) { - bgColor = list.getSelectionBackground(); - fgColor = list.getSelectionForeground(); - } else { // not selected - bgColor = list.getBackground(); - if ((index % 2) == 0) { // every second item slightly different - bgColor = new Color( - Math.abs(bgColor.getRed() - DARKER_COLOR_COMPONENT), - Math.abs(bgColor.getGreen() - DARKER_COLOR_COMPONENT), - Math.abs(bgColor.getBlue() - DARKER_COLOR_COMPONENT) - ); - } - fgColor = list.getForeground(); + Color bgColor = list.getBackground(); + Color bgSelectedColor = list.getSelectionBackground(); + Color fgColor = list.getForeground(); + Color fgSelectedColor = list.getSelectionForeground(); + if ((index % 2) == 0) { // every second item slightly different + bgColor = new Color( + Math.abs(bgColor.getRed() - DARKER_COLOR_COMPONENT), + Math.abs(bgColor.getGreen() - DARKER_COLOR_COMPONENT), + Math.abs(bgColor.getBlue() - DARKER_COLOR_COMPONENT) + ); } + + renderComponent.setColors(fgColor, bgColor, fgSelectedColor, bgSelectedColor); // quick check Component.setBackground() always fires change if (renderComponent.getBackground() != bgColor) { renderComponent.setBackground(bgColor); @@ -123,7 +127,7 @@ return renderComponent; } else { - return defaultRenderer.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus); + return defaultRenderer.getListCellRendererComponent( list, value, index, isBestMatch, cellHasFocus); } } }); @@ -148,8 +152,9 @@ } } - void setData(List data) { + void setData(List data, @NonNull CompletionController controller) { smartIndex = -1; + this.controller = controller; if (data != null) { int itemCount = data.size(); ListCellRenderer renderer = getCellRenderer(); @@ -179,7 +184,7 @@ setModel(lm); if (itemCount > 0) { - setSelectedIndex(0); + setSelection(0, false); } int visibleRowCount = Math.min(itemCount, maxVisibleRowCount); setVisibleRowCount(visibleRowCount); @@ -214,6 +219,19 @@ updateAccessible(); } } + + public @NonNull CompletionController.Selection getSelection() { + return new CompletionController.Selection(getSelectedIndex(), isSelected); + } + + public void setSelection(@NonNull CompletionController.Selection selection) { + setSelection(selection.getIndex(), selection.isSelected()); + } + + public void setSelection(int index, boolean isSelected) { + this.isSelected = isSelected; + setSelectedIndex(index); + } private JLabel accessibleLabel; private JLabel accessibleFakeLabel; @@ -241,7 +259,7 @@ int idx = (getSelectedIndex() - 1 + size) % size; while(idx > 0 && getModel().getElementAt(idx) == null) idx--; - setSelectedIndex(idx); + setSelection(idx, true); ensureIndexIsVisible(idx); } } @@ -254,7 +272,7 @@ idx++; if (idx == size) idx = 0; - setSelectedIndex(idx); + setSelection(idx, true); ensureIndexIsVisible(idx); } } @@ -265,7 +283,7 @@ int idx = Math.max(getSelectedIndex() - pageSize, 0); while(idx > 0 && getModel().getElementAt(idx) == null) idx--; - setSelectedIndex(idx); + setSelection(idx, true); ensureIndexIsVisible(idx); } } @@ -282,14 +300,14 @@ while(idx > 0 && getModel().getElementAt(idx) == null) idx--; } - setSelectedIndex(idx); + setSelection(idx, true); ensureIndexIsVisible(idx); } } public void begin() { if (getModel().getSize() > 0) { - setSelectedIndex(0); + setSelection(0, true); ensureIndexIsVisible(0); } } @@ -300,7 +318,7 @@ int idx = size - 1; while(idx > 0 && getModel().getElementAt(idx) == null) idx--; - setSelectedIndex(idx); + setSelection(idx, true); ensureIndexIsVisible(idx); } } @@ -323,18 +341,25 @@ } private final class RenderComponent extends JComponent { - + private CompletionItem item; - private boolean selected; + private boolean isBestMatch; + private boolean isSelected; private boolean separator; + + private Color fgColor; + private Color bgColor; + private Color fgSelectedColor; + private Color bgSelectedColor; void setItem(CompletionItem item) { this.item = item; } - void setSelected(boolean selected) { - this.selected = selected; + void setSelected(boolean isBestMatch, boolean isSelected) { + this.isBestMatch = isBestMatch; + this.isSelected = isSelected; } void setSeparator(boolean separator) { @@ -349,18 +374,12 @@ // of the widest item). // Therefore the item's render width is taken from the viewport's width. int itemRenderWidth = ((JViewport)CompletionJList.this.getParent()).getWidth(); - Color bgColor = getBackground(); - Color fgColor = getForeground(); int height = getHeight(); - // Clear the background - g.setColor(bgColor); - g.fillRect(0, 0, itemRenderWidth, height); - g.setColor(fgColor); - // Render the item - item.render(g, CompletionJList.this.getFont(), getForeground(), bgColor, - itemRenderWidth, getHeight(), selected); + controller.render(g, CompletionJList.this.getFont(), fgColor, bgColor, + fgSelectedColor, bgSelectedColor, itemRenderWidth, getHeight(), + item, isBestMatch, isSelected); if (separator) { g.setColor(Color.gray); @@ -368,7 +387,7 @@ g.setColor(fgColor); } } - + public @Override Dimension getPreferredSize() { if (cellPreferredSizeGraphics == null) { // CompletionJList.this.getGraphics() is null @@ -381,6 +400,13 @@ fixedItemHeight); } + private void setColors(Color fgColor, Color bgColor, Color fgSelectedColor, Color bgSelectedColor) { + this.fgColor = fgColor; + this.bgColor = bgColor; + this.fgSelectedColor = fgSelectedColor; + this.bgSelectedColor = bgSelectedColor; + } + } } diff -r 81cafa2674cb -r 31225ddf6f59 editor.completion/src/org/netbeans/modules/editor/completion/CompletionLayout.java --- a/editor.completion/src/org/netbeans/modules/editor/completion/CompletionLayout.java Sat Feb 25 17:26:54 2012 +0100 +++ b/editor.completion/src/org/netbeans/modules/editor/completion/CompletionLayout.java Sat Feb 25 13:00:55 2012 -0600 @@ -71,9 +71,10 @@ import javax.swing.event.ListSelectionListener; import javax.swing.text.Document; import javax.swing.text.JTextComponent; +import org.netbeans.api.annotations.common.CheckForNull; import org.netbeans.editor.GuardedDocument; +import org.netbeans.spi.editor.completion.CompletionController; import org.netbeans.spi.editor.completion.CompletionDocumentation; -import org.netbeans.spi.editor.completion.CompletionItem; import org.openide.text.CloneableEditorSupport; import org.openide.util.NbBundle; @@ -138,8 +139,10 @@ } public void showCompletion(List data, String title, int anchorOffset, - ListSelectionListener listSelectionListener, String additionalItemsText, String shortcutHint, int selectedIndex) { - completionPopup.show(data, title, anchorOffset, listSelectionListener, additionalItemsText, shortcutHint, selectedIndex); + ListSelectionListener listSelectionListener, String additionalItemsText, + String shortcutHint, CompletionController controller, + CompletionController.Selection selection) { + completionPopup.show(data, title, anchorOffset, listSelectionListener, additionalItemsText, shortcutHint, controller, selection); if (!visiblePopups.contains(completionPopup)) visiblePopups.push(completionPopup); } @@ -159,7 +162,7 @@ return completionPopup.isVisible(); } - public CompletionItem getSelectedCompletionItem() { + public @CheckForNull SelectedCompletionItem getSelectedCompletionItem() { return completionPopup.getSelectedCompletionItem(); } @@ -305,7 +308,9 @@ private CompletionScrollPane completionScrollPane; public void show(List data, String title, int anchorOffset, - ListSelectionListener listSelectionListener, String additionalItemsText, String shortcutHint, int selectedIndex) { + ListSelectionListener listSelectionListener, String additionalItemsText, + String shortcutHint, final CompletionController controller, + CompletionController.Selection selection) { JTextComponent editorComponent = getEditorComponent(); if (editorComponent == null) { @@ -330,7 +335,7 @@ JTextComponent c = getEditorComponent(); if (SwingUtilities.isLeftMouseButton(evt)) { if (c != null && evt.getClickCount() == 2 ) { - CompletionItem selectedItem + SelectedCompletionItem selectedItem = completionScrollPane.getSelectedCompletionItem(); if (selectedItem != null) { Document doc = c.getDocument(); @@ -342,7 +347,7 @@ CompletionImpl.uilog(r); CompletionImpl.sendUndoableEdit(doc, CloneableEditorSupport.BEGIN_COMMIT_GROUP); try { - selectedItem.defaultAction(c); + controller.defaultAction(selectedItem.getItem(), selectedItem.isSelected()); } finally { CompletionImpl.sendUndoableEdit(doc, CloneableEditorSupport.END_COMMIT_GROUP); } @@ -372,7 +377,7 @@ } // Set the new data getPreferredSize(); - completionScrollPane.setData(data, title, selectedIndex); + completionScrollPane.setData(data, title, controller, selection); setAnchorOffset(anchorOffset); Dimension prefSize = getPreferredSize(); @@ -395,7 +400,7 @@ } // otherwise present popup size will be retained } - public CompletionItem getSelectedCompletionItem() { + public @CheckForNull SelectedCompletionItem getSelectedCompletionItem() { return isVisible() ? completionScrollPane.getSelectedCompletionItem() : null; } diff -r 81cafa2674cb -r 31225ddf6f59 editor.completion/src/org/netbeans/modules/editor/completion/CompletionResultSetImpl.java --- a/editor.completion/src/org/netbeans/modules/editor/completion/CompletionResultSetImpl.java Sat Feb 25 17:26:54 2012 +0100 +++ b/editor.completion/src/org/netbeans/modules/editor/completion/CompletionResultSetImpl.java Sat Feb 25 13:00:55 2012 -0600 @@ -213,7 +213,7 @@ public synchronized void setHasAdditionalItems(boolean value) { checkNotFinished(); - if (queryType != CompletionProvider.COMPLETION_QUERY_TYPE) { + if ((queryType & CompletionProvider.COMPLETION_ALL_QUERY_TYPE) == CompletionProvider.COMPLETION_ALL_QUERY_TYPE) { return; } this.hasAdditionalItems = value; @@ -225,7 +225,7 @@ public synchronized void setHasAdditionalItemsText(String text) { checkNotFinished(); - if (queryType != CompletionProvider.COMPLETION_QUERY_TYPE) { + if ((queryType & CompletionProvider.COMPLETION_ALL_QUERY_TYPE) == CompletionProvider.COMPLETION_ALL_QUERY_TYPE) { return; } this.hasAdditionalItemsText = text; @@ -237,7 +237,7 @@ public synchronized void setDocumentation(CompletionDocumentation documentation) { checkNotFinished(); - if (!active || queryType != CompletionProvider.DOCUMENTATION_QUERY_TYPE) { + if (!active || (queryType & CompletionProvider.DOCUMENTATION_QUERY_TYPE) != CompletionProvider.DOCUMENTATION_QUERY_TYPE) { return; } this.documentation = documentation; @@ -253,7 +253,7 @@ public synchronized void setToolTip(JToolTip toolTip) { checkNotFinished(); - if (!active || queryType != CompletionProvider.TOOLTIP_QUERY_TYPE) { + if (!active || (queryType & CompletionProvider.TOOLTIP_QUERY_TYPE) != CompletionProvider.TOOLTIP_QUERY_TYPE) { return; } this.toolTip = toolTip; diff -r 81cafa2674cb -r 31225ddf6f59 editor.completion/src/org/netbeans/modules/editor/completion/CompletionScrollPane.java --- a/editor.completion/src/org/netbeans/modules/editor/completion/CompletionScrollPane.java Sat Feb 25 17:26:54 2012 +0100 +++ b/editor.completion/src/org/netbeans/modules/editor/completion/CompletionScrollPane.java Sat Feb 25 13:00:55 2012 -0600 @@ -65,8 +65,10 @@ import javax.swing.text.JTextComponent; import javax.swing.text.Keymap; import javax.swing.text.EditorKit; +import org.netbeans.api.annotations.common.CheckForNull; import org.netbeans.editor.BaseKit; import org.netbeans.editor.ext.ExtKit; +import org.netbeans.spi.editor.completion.CompletionController; import org.netbeans.spi.editor.completion.CompletionItem; /** @@ -122,11 +124,14 @@ installKeybindings(editorComponent); } - public void setData(List data, String title, int selectedIndex) { + public void setData(List data, + String title, + CompletionController controller, + CompletionController.Selection selection) { dataObj = data; - view.setData(data); - view.setSelectedIndex(selectedIndex); - Rectangle r = view.getCellBounds(selectedIndex, selectedIndex); + view.setData(data, controller); + view.setSelection(selection); + Rectangle r = view.getCellBounds(selection.getIndex(), selection.getIndex()); if (r != null) view.scrollRectToVisible(r); setTitle(title); @@ -139,9 +144,14 @@ setViewportView(getViewport().getView()); } - public CompletionItem getSelectedCompletionItem() { + public @CheckForNull SelectedCompletionItem getSelectedCompletionItem() { Object ret = view.getSelectedValue(); - return ret instanceof CompletionItem ? (CompletionItem) ret : null; + if (ret instanceof CompletionItem) { + boolean isSelected = view.getSelection().isSelected(); + return new SelectedCompletionItem((CompletionItem)ret, isSelected); + } + + return null; } public int getSelectedIndex() { diff -r 81cafa2674cb -r 31225ddf6f59 editor.completion/src/org/netbeans/modules/editor/completion/DefaultCompletionControllerProvider.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/editor.completion/src/org/netbeans/modules/editor/completion/DefaultCompletionControllerProvider.java Sat Feb 25 13:00:55 2012 -0600 @@ -0,0 +1,62 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2011 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2011 Sun Microsystems, Inc. + */ +package org.netbeans.modules.editor.completion; + +import javax.swing.text.JTextComponent; +import org.netbeans.spi.editor.completion.BaseCompletionController; +import org.netbeans.spi.editor.completion.CompletionController; +import org.netbeans.spi.editor.completion.CompletionControllerProvider; +import org.netbeans.spi.editor.completion.CompletionTask; + +/** + * + * @author Sam Harwell + */ +public class DefaultCompletionControllerProvider implements CompletionControllerProvider { + public static final DefaultCompletionControllerProvider INSTANCE = new DefaultCompletionControllerProvider(); + + @Override + public CompletionController createController(JTextComponent component, CompletionTask task, int queryType) { + return new BaseCompletionController(component, queryType); + } + +} diff -r 81cafa2674cb -r 31225ddf6f59 editor.completion/src/org/netbeans/modules/editor/completion/SelectedCompletionItem.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/editor.completion/src/org/netbeans/modules/editor/completion/SelectedCompletionItem.java Sat Feb 25 13:00:55 2012 -0600 @@ -0,0 +1,66 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2011 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2011 Sun Microsystems, Inc. + */ +package org.netbeans.modules.editor.completion; + +import org.netbeans.spi.editor.completion.CompletionItem; + +/** + * + * @author Sam Harwell + */ +public final class SelectedCompletionItem { + private final CompletionItem item; + private final boolean selected; + + public SelectedCompletionItem(CompletionItem item, boolean selected) { + this.item = item; + this.selected = selected; + } + + public CompletionItem getItem() { + return item; + } + + public boolean isSelected() { + return selected; + } +} diff -r 81cafa2674cb -r 31225ddf6f59 editor.completion/src/org/netbeans/spi/editor/completion/BaseCompletionController.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/editor.completion/src/org/netbeans/spi/editor/completion/BaseCompletionController.java Sat Feb 25 13:00:55 2012 -0600 @@ -0,0 +1,225 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2011 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2011 Sun Microsystems, Inc. + */ +package org.netbeans.spi.editor.completion; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.event.KeyEvent; +import java.util.Collections; +import java.util.List; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.annotations.common.NullAllowed; +import org.netbeans.editor.BaseDocument; +import org.netbeans.editor.Utilities; +import org.openide.util.Parameters; + +/** + * + * @author Sam Harwell + */ +public class BaseCompletionController implements CompletionController { + private final JTextComponent component; + private final int queryType; + + /** + * + * @param component The active component. + * @param queryType The query type. + * + * @throws NullPointerException if the parameter value is null. + */ + public BaseCompletionController(@NonNull JTextComponent component, + int queryType) { + Parameters.notNull("component", component); + this.component = component; + this.queryType = queryType; + } + + + /** + * Gets the active {@link JTextComponent}. + * + * @return The active component. + */ + public @NonNull JTextComponent getComponent() { + return component; + } + + /** + * Gets the active {@link Document}. + * + * @return The active document. + */ + public Document getDocument() { + return getComponent().getDocument(); + } + + @Override + public void sortItems(List items, int sortType) { + Collections.sort(items, CompletionItemComparator.get(sortType)); + } + + @Override + public Selection getSelection(List items) { + String prefix = getCompletionPrefix(); + if (prefix != null && prefix.length() > 0) { + for (int idx = 0; idx < items.size(); idx++) { + CompletionItem item = items.get(idx); + CharSequence text = item.getInsertPrefix(); + if (text != null && text.toString().startsWith(prefix)) { + boolean selected = isSelected(items, prefix, idx); + boolean unique = isUnique(items, prefix, idx); + return new Selection(idx, selected, unique); + } + } + } + + /* This follows existing behavior, which is slightly different from + * Selection.DEFAULT because this method marks the item as selected. + */ + return new Selection(0); + } + + @Override + public void defaultAction(CompletionItem bestMatch, boolean isSelected) { + if (isSelected) { + bestMatch.defaultAction(component); + } + } + + @Override + public void processKeyEvent(KeyEvent evt, CompletionItem bestMatch, boolean isSelected) { + bestMatch.processKeyEvent(evt); + } + + @Override + public void render(Graphics g, Font defaultFont, Color foregroundColor, + Color backgroundColor, Color selectedForegroundColor, + Color selectedBackgroundColor, int width, int height, CompletionItem item, boolean isBestMatch, boolean isSelected) { + if (isBestMatch) { + // Clear the background + g.setColor(selectedBackgroundColor); + g.fillRect(0, 0, width, height); + g.setColor(selectedForegroundColor); + item.render(g, defaultFont, selectedForegroundColor, selectedBackgroundColor, width, height, isBestMatch); + } else { + // Clear the background + g.setColor(backgroundColor); + g.fillRect(0, 0, width, height); + g.setColor(foregroundColor); + item.render(g, defaultFont, foregroundColor, backgroundColor, width, height, isBestMatch); + } + } + + @Override + public boolean instantSubstitution(CompletionItem uniqueMatch) { + return uniqueMatch.instantSubstitution(component); + } + + /** + * Gets the current completion prefix. The default implementation returns the + * text of the current identifier (per {@link Utilities#getIdentifierBlock}) + * up to the location of the caret. + * + * @return The completion prefix. + */ + protected @CheckForNull String getCompletionPrefix() { + if(getDocument() instanceof BaseDocument) { + BaseDocument doc = (BaseDocument)getDocument(); + int caretOffset = getComponent().getSelectionStart(); + try { + int[] block = Utilities.getIdentifierBlock(doc, caretOffset); + if (block != null) { + block[1] = caretOffset; + return doc.getText(block); + } + } catch (BadLocationException ble) { + } + } + + return null; + } + + /** + * + * @param items + * @param prefix + * @param index + * + * @return true if the item is selected, otherwise false. + */ + protected boolean isSelected(@NonNull List items, + @NullAllowed String prefix, + int index) { + return true; + } + + /** + * + * @param items + * @param prefix + * @param index + * + * @return true if the selection is unique, otherwise false. + */ + protected boolean isUnique(@NonNull List items, + @NullAllowed String prefix, + int index) { + if (items.size() != 1) { + return false; + } + + try { + int caretOffset = getComponent().getSelectionStart(); + int[] block = Utilities.getIdentifierBlock(getComponent(), + getComponent().getSelectionStart()); + return block == null || block[1] == caretOffset; + } catch (BadLocationException ble) { + return false; + } + } +} diff -r 81cafa2674cb -r 31225ddf6f59 editor.completion/src/org/netbeans/spi/editor/completion/CompletionController.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/editor.completion/src/org/netbeans/spi/editor/completion/CompletionController.java Sat Feb 25 13:00:55 2012 -0600 @@ -0,0 +1,236 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2011 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2011 Sun Microsystems, Inc. + */ +package org.netbeans.spi.editor.completion; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.event.KeyEvent; +import java.util.List; +import org.netbeans.api.annotations.common.NonNull; + +/** + * This interface controls the sorting and selection behavior of the Code + * Completion drop down. + * + * @author Sam Harwell + */ +public interface CompletionController { + + /** + * Sorts the specified list of CompletionItem items. + * + * @param items The list of items to sort. + * @param sortType The desired sort type, one of + * {@link CompletionResultSet#PRIORITY_SORT_TYPE} or + * {@link CompletionResultSet#TEXT_SORT_TYPE}. + */ + void sortItems(@NonNull List items, int sortType); + + /** + * Gets the initial selected item from a list of CompletionItem items shown + * in a code completion drop down. The initial selection represents a "best + * match" among the available items. + * + * @param items A list of all CompletionItem items shown in the code + * completion drop down. + * + * @return A {@link Selection} object containing information about the + * initial selection. + */ + @NonNull Selection getSelection(@NonNull List items); + + /** + * Gets invoked when user presses VK_ENTER key + * or when she double-clicks on this item with the mouse cursor. + *
+ * This method gets invoked from AWT thread. + * + * @param bestMatch non-null completion item for which the action is invoked + * @param isSelected whether or not the best match is currently selected + */ + void defaultAction(@NonNull CompletionItem bestMatch, boolean isSelected); + + /** + * Process the key pressed when this completion item was selected + * in the completion popup window. + *
+ * This method gets invoked from AWT thread. + * + * @param evt non-null key event of the pressed key. It should be consumed + * in case the item is sensitive to the given key. The source of this + * event is the text component to which the corresponding action should + * be performed. + * @param bestMatch non-null completion item which is the current best match + * @param isSelected whether or not the best match is currently selected + */ + void processKeyEvent(@NonNull KeyEvent evt, @NonNull CompletionItem bestMatch, boolean isSelected); + + /** + * Render this item into the given graphics. + * + * @param g graphics to render the item into. + * @param defaultFont default font used for rendering. + * @param foregroundColor foreground color used for rendering. + * @param backgroundColor color used for background. + * @param selectedForegroundColor foreground color used for rendering selected items. + * @param selectedBackgroundColor color used for background selected items. + * @param width width of the area to render into. + * @param height height of the are to render into. + * @param item non-null completion item to render. + * @param isBestMatch true if this item is the best match. + * @param isSelected true if this item is selected. + */ + void render(@NonNull Graphics g, @NonNull Font defaultFont, @NonNull Color foregroundColor, + @NonNull Color backgroundColor, @NonNull Color selectedForegroundColor, + @NonNull Color selectedBackgroundColor, int width, int height, @NonNull CompletionItem item, + boolean isBestMatch, boolean isSelected); + + /** + * When enabled for the item the instant substitution should process the item + * in the same way like when the item is displayed and Enter key gets pressed + * by the user. + *
+ * Instant substitution is invoked when there would be just a single item + * displayed in the completion popup window. + *
+ * The implementation can invoke the {@link #defaultAction(JTextComponent)} + * if necessary. + *
+ * This method gets invoked from AWT thread. + * + * @param uniqueMatch non-null unique completion item to perform the instant + * substitution. + * @return true if the instant substitution was successfully done. + * false means that the instant substitution should not be done + * for this item and the completion item should normally be displayed. + */ + boolean instantSubstitution(@NonNull CompletionItem uniqueMatch); + + /** + * Represents an initial selected CompletionItem for code completion. This + * item can be seen as the best match for a given input. + */ + public static final class Selection { + /** + * The default selected item. The item has index 0 and is not selected + * or unique. + */ + public static final Selection DEFAULT = new Selection(0, false, false); + + private final int index; + private final boolean selected; + private final boolean unique; + + /** + * Initializes a Selection with the specified index. The item is selected + * but not unique. + * + * @param index The index of the selected CompletionItem. + * + * @throws IllegalArgumentException when the index argument is less than zero. + */ + public Selection(int index) { + this(index, true, false); + } + + /** + * Initializes a Selection with the specified index and selected state. + * The item is not unique. + * + * @param index The index of the selected CompletionItem. + * @param selected true if the match is selected, otherwise + * false. + * + * @throws IllegalArgumentException when the index argument is less than zero. + */ + public Selection(int index, boolean selected) { + this(index, selected, false); + } + + /** + * Initializes a Selection with the specified index, selected state, and + * uniqueness. + * + * @param index The index of the selected CompletionItem. + * @param selected true if the match is selected, otherwise + * false. + * @param unique true if the match is unique, otherwise + * false. + * + * @throws IllegalArgumentException when the index argument is less than zero. + */ + public Selection(int index, boolean selected, boolean unique) { + if (index < 0) { + throw new IllegalArgumentException("The index cannot be negative."); // NOI18N + } + + this.index = index; + this.selected = selected; + this.unique = unique; + } + + /** + * + * @return The index of the selected CompletionItem. + */ + public int getIndex() { + return index; + } + + /** + * + * @return true if the match is selected, otherwise false. + */ + public boolean isSelected() { + return selected; + } + + /** + * + * @return true if the match is unique, otherwise false. + */ + public boolean isUnique() { + return unique; + } + } +} diff -r 81cafa2674cb -r 31225ddf6f59 editor.completion/src/org/netbeans/spi/editor/completion/CompletionControllerProvider.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/editor.completion/src/org/netbeans/spi/editor/completion/CompletionControllerProvider.java Sat Feb 25 13:00:55 2012 -0600 @@ -0,0 +1,73 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2011 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2011 Sun Microsystems, Inc. + */ +package org.netbeans.spi.editor.completion; + +import javax.swing.text.JTextComponent; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.spi.editor.mimelookup.MimeLocation; + +/** + * + * @author Sam Harwell + */ +@MimeLocation(subfolderName="CompletionProviders") +public interface CompletionControllerProvider { + + /** + * Creates a {@link CompletionController} which provides behavior for the + * code completion drop down. + * + * @param component The active component. + * @param task The completion task. + * @param queryType The completion query type. + * + * @return A {@link CompletionController} to provide behavior for the completion drop + * down, or null if this provider does not support the specified + * component, task, or query type. + */ + public @CheckForNull CompletionController createController( + @NonNull JTextComponent component, + @NonNull CompletionTask task, + int queryType); + +} diff -r 81cafa2674cb -r 31225ddf6f59 editor.completion/src/org/netbeans/spi/editor/completion/CompletionItemComparator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/editor.completion/src/org/netbeans/spi/editor/completion/CompletionItemComparator.java Sat Feb 25 13:00:55 2012 -0600 @@ -0,0 +1,195 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun + * Microsystems, Inc. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ + +package org.netbeans.spi.editor.completion; + +import java.util.Comparator; +import org.netbeans.api.annotations.common.NonNull; +import org.openide.util.Parameters; + +/** + * Comparator for completion items either by sort priority or by sort text. + * + * @author Dusan Balek, Miloslav Metelka + */ + +public class CompletionItemComparator implements Comparator { + + /** + * A Comparator which sorts CompletionItem items by priority, then alphabetically. + */ + public static final Comparator BY_PRIORITY = + new CompletionItemComparator(new PriorityComparator(), new TextComparator()); + + /** + * A Comparator which sorts CompletionItem items alphabetically, then by priority. + */ + public static final Comparator ALPHABETICAL = + new CompletionItemComparator(new TextComparator(), new PriorityComparator()); + + private final Comparator primaryComparator; + private final Comparator secondaryComparator; + + /** + * Constructs a CompletionItemComparator from the specified primary and secondary + * comparators. + * + * @param primaryComparator non-null primary comparator. + * @param secondaryComparator non-null secondary comparator. + */ + public CompletionItemComparator(@NonNull Comparator primaryComparator, + @NonNull Comparator secondaryComparator) { + Parameters.notNull("primaryComparator", primaryComparator); + Parameters.notNull("secondaryComparator", secondaryComparator); + this.primaryComparator = primaryComparator; + this.secondaryComparator = secondaryComparator; + } + + /** + * Gets a default comparator that can be used for sorting CompletionItem lists + * for the specified sort type. + * + * @param sortType The sort type, one of {@link CompletionResultSet#PRIORITY_SORT_TYPE} + * or {@link CompletionResultSet#TEXT_SORT_TYPE}. + * + * @return The Comparator to use when sorting CompletionItem items. + * + * @throws IllegalArgumentException the sortType argument is not a supported type. + */ + public static Comparator get(int sortType) { + if (sortType == CompletionResultSet.PRIORITY_SORT_TYPE) + return BY_PRIORITY; + + if (sortType == CompletionResultSet.TEXT_SORT_TYPE) + return ALPHABETICAL; + + throw new IllegalArgumentException(); + } + + @Override + public int compare(CompletionItem i1, CompletionItem i2) { + if (i1 == i2) + return 0; + + int primaryDiff = primaryComparator.compare(i1, i2); + if (primaryDiff != 0) { + return primaryDiff; + } + + return secondaryComparator.compare(i1, i2); + } + + /** + * A comparator which compares CompletionItem items by priority. + * + * @see CompletionItem#getSortPriority() + */ + protected static class PriorityComparator implements Comparator { + + /** + * Initializes a PriorityComparator. + */ + public PriorityComparator() { + } + + @Override + public int compare(CompletionItem o1, CompletionItem o2) { + if (o1 == o2) { + return 0; + } + + return o1.getSortPriority() - o2.getSortPriority(); + } + + } + + /** + * A Comparator which compares CompletionItem items by text. + * + * @see CompletionItem#getSortText() + */ + protected static class TextComparator implements Comparator { + + /** + * Initializes a TextComparator. + */ + public TextComparator() { + } + + @Override + public int compare(CompletionItem o1, CompletionItem o2) { + return compareText(o1.getSortText(), o2.getSortText()); + } + + /** + * Compares two CharSequence objects for order. + * + * @param text1 the first text to be compared + * @param text2 the second text to be compared + * @return a negative integer, zero, or a positive integer as the + * first argument is less than, equal to, or greater than the + * second. + */ + protected int compareText(CharSequence text1, CharSequence text2) { + if (text1 == null) + text1 = ""; //NOI18N + + if (text2 == null) + text2 = ""; //NOI18N + + int len = Math.min(text1.length(), text2.length()); + for (int i = 0; i < len; i++) { + char ch1 = text1.charAt(i); + char ch2 = text2.charAt(i); + if (ch1 != ch2) { + return ch1 - ch2; + } + } + + return text1.length() - text2.length(); + } + + } + +} diff -r 81cafa2674cb -r 31225ddf6f59 editor.completion/src/org/netbeans/spi/editor/completion/CompletionProvider.java --- a/editor.completion/src/org/netbeans/spi/editor/completion/CompletionProvider.java Sat Feb 25 17:26:54 2012 +0100 +++ b/editor.completion/src/org/netbeans/spi/editor/completion/CompletionProvider.java Sat Feb 25 13:00:55 2012 -0600 @@ -67,22 +67,37 @@ /** * The int value representing the query for a code completion. */ - public static final int COMPLETION_QUERY_TYPE = 1; + public static final int COMPLETION_QUERY_TYPE = 0x01; /** * The int value representing the query for a documentation. */ - public static final int DOCUMENTATION_QUERY_TYPE = 2; + public static final int DOCUMENTATION_QUERY_TYPE = 0x02; /** * The int value representing the query for a tooltip hint. */ - public static final int TOOLTIP_QUERY_TYPE = 4; + public static final int TOOLTIP_QUERY_TYPE = 0x04; /** * The int value representing the query for an all code completion. */ - public static final int COMPLETION_ALL_QUERY_TYPE = 9; + public static final int COMPLETION_ALL_QUERY_TYPE = 0x11; + + /** + * The int mask representing the bits used for a completion query type. + */ + public static final int COMPLETION_QUERY_MASK = COMPLETION_QUERY_TYPE | COMPLETION_ALL_QUERY_TYPE; + + /** + * The int mask of bits a user can specify to customize the query type. + */ + public static final int USER_QUERY_MASK = 0xFFFF0000; + + /** + * The int mask of bits used by the API to specify the query type. + */ + public static final int RESERVED_QUERY_MASK = ~USER_QUERY_MASK; /** * Creates a task that performs a query of the given type on the given component. diff -r 81cafa2674cb -r 31225ddf6f59 editor.completion/src/org/netbeans/spi/editor/completion/CompletionResultSet.java --- a/editor.completion/src/org/netbeans/spi/editor/completion/CompletionResultSet.java Sat Feb 25 17:26:54 2012 +0100 +++ b/editor.completion/src/org/netbeans/spi/editor/completion/CompletionResultSet.java Sat Feb 25 13:00:55 2012 -0600 @@ -45,6 +45,8 @@ package org.netbeans.spi.editor.completion; import java.util.Collection; +import java.util.Collections; +import java.util.List; import javax.swing.JToolTip; import org.netbeans.modules.editor.completion.CompletionResultSetImpl; import org.netbeans.modules.editor.completion.CompletionSpiPackageAccessor; @@ -101,19 +103,38 @@ } /** - * Set the document offset to which the returned completion items - * or documentation or tooltip should be anchored. + * Get the document offset to which the returned completion items, + * documentation, or tool tip should be anchored. *
* If there will be multiple completion providers setting this property * for the given mime-type then only the first one * (according to the xml-layer registration order) * will be taken into account. + * @return + */ + public int getAnchorOffset() { + return impl.getAnchorOffset(); + } + + /** + * Set the document offset to which the returned completion items + * or documentation or tooltip should be anchored. + * + * @see #getAnchorOffset() */ public void setAnchorOffset(int anchorOffset) { impl.setAnchorOffset(anchorOffset); } /** + * Gets an unmodifiable list of completion items in this result set. + * @return + */ + public List getItems() { + return Collections.unmodifiableList(impl.getItems()); + } + + /** * Add the completion item to this result set. *
* This method can be called multiple times until diff -r 81cafa2674cb -r 31225ddf6f59 editor.completion/src/org/netbeans/spi/editor/completion/support/AsyncCompletionTask.java --- a/editor.completion/src/org/netbeans/spi/editor/completion/support/AsyncCompletionTask.java Sat Feb 25 17:26:54 2012 +0100 +++ b/editor.completion/src/org/netbeans/spi/editor/completion/support/AsyncCompletionTask.java Sat Feb 25 13:00:55 2012 -0600 @@ -47,6 +47,9 @@ import javax.swing.SwingUtilities; import javax.swing.text.Document; import javax.swing.text.JTextComponent; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.annotations.common.NullAllowed; import org.netbeans.spi.editor.completion.CompletionResultSet; import org.netbeans.spi.editor.completion.CompletionTask; import org.openide.util.RequestProcessor; @@ -97,7 +100,7 @@ *
* It may be null to indicate that no component was provided. */ - public AsyncCompletionTask(AsyncCompletionQuery query, JTextComponent component) { + public AsyncCompletionTask(@NonNull AsyncCompletionQuery query, @NullAllowed JTextComponent component) { assert (query != null) : "Query must be non-null"; this.query = query; this.component = component; @@ -110,11 +113,29 @@ * * @param query non-null query implementation. */ - public AsyncCompletionTask(AsyncCompletionQuery query) { + public AsyncCompletionTask(@NonNull AsyncCompletionQuery query) { this(query, null); } /** + * Gets the {@link AsyncCompletionQuery} wrapped by this task. + * + * @return The {@link AsyncCompletionQuery}. + */ + public @NonNull AsyncCompletionQuery getQuery() { + return query; + } + + /** + * Gets the active component used by this task. + * + * @return The component. + */ + public @CheckForNull JTextComponent getComponent() { + return component; + } + + /** * Called by completion infrastructure in AWT thread to populate * the given result set with data. */ diff -r 81cafa2674cb -r 31225ddf6f59 editor.completion/src/org/netbeans/spi/editor/completion/support/CompletionUtilities.java --- a/editor.completion/src/org/netbeans/spi/editor/completion/support/CompletionUtilities.java Sat Feb 25 17:26:54 2012 +0100 +++ b/editor.completion/src/org/netbeans/spi/editor/completion/support/CompletionUtilities.java Sat Feb 25 13:00:55 2012 -0600 @@ -49,6 +49,8 @@ import java.awt.FontMetrics; import java.awt.Graphics; import javax.swing.ImageIcon; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.annotations.common.NullAllowed; import org.netbeans.modules.editor.completion.PatchedHtmlRenderer; /** @@ -119,7 +121,7 @@ } return width; } - + /** * Render a completion item using the provided icon and left and right * html texts. @@ -160,30 +162,98 @@ public static void renderHtml(ImageIcon icon, String leftHtmlText, String rightHtmlText, Graphics g, Font defaultFont, Color defaultColor, int width, int height, boolean selected) { + renderHtml(icon, leftHtmlText, rightHtmlText, g, defaultFont, defaultColor, defaultColor, width, height, selected, true); + } + + /** + * Render a completion item using the provided icon and left and right + * html texts. + * + * @param icon icon 16x16 that will be displayed on the left. It may be null + * which means that no icon will be displayed but the space for the icon + * will still be reserved (to properly align with other items + * that will provide an icon). + * + * @param leftHtmlText html text that will be displayed on the left side + * of the item's rendering area next to the icon. + *
+ * It may be null which indicates that no left text will be displayed. + *
+ * If there's not enough horizontal space in the rendering area + * the text will be shrinked and "..." will be displayed at the end. + * + * @param rightHtmlText html text that will be aligned to the right edge + * of the item's rendering area. + *
+ * It may be null which means that no right text will be displayed. + *
+ * The right text is always attempted to be fully displayed unlike + * the left text that may be shrinked if there's not enough rendering space + * in the horizontal direction. + *
+ * If there's not enough space even for the right text it will be shrinked + * and "..." will be displayed at the end of the rendered string. + * @param g non-null graphics through which the rendering happens. + * @param defaultFont non-null default font to be used for rendering. + * @param foregroundColor non-null default color to be used for rendering. + * @param selectedBackgroundColor non-null color to be used a the background + * for rendering selected items. + * @param width >=0 available width for rendering. + * @param height >=0 available height for rendering. + * @param isBestMatch whether the item being rendered is currently the best + * match in the completion's JList. If selected the foreground color is forced + * to be black for all parts of the rendered strings. + * @param isSelected whether the item being rendered is currently selected in + * the completion's JList. If true, the foreground color is + * forced to the value of the foregroundColor argument for all parts of the + * rendered strings. + */ + public static void renderHtml(@NullAllowed ImageIcon icon, + @NullAllowed String leftHtmlText, + @NullAllowed String rightHtmlText, + @NonNull Graphics g, + @NonNull Font defaultFont, + @NonNull Color foregroundColor, + @NonNull Color selectedBackgroundColor, + int width, + int height, + boolean isBestMatch, + boolean isSelected) { if (icon != null) { // The image of the ImageIcon should already be loaded // so no ImageObserver should be necessary boolean done = g.drawImage(icon.getImage(), BEFORE_ICON_GAP, (height - icon.getIconHeight()) /2, null); assert (done); } + + if (isBestMatch && isSelected) { + g.setColor(foregroundColor); + } + int iconWidth = BEFORE_ICON_GAP + ICON_WIDTH + AFTER_ICON_GAP; int rightTextX = width - AFTER_RIGHT_TEXT_GAP; FontMetrics fm = g.getFontMetrics(defaultFont); int textY = (height - fm.getHeight())/2 + fm.getHeight() - fm.getDescent(); if (rightHtmlText != null && rightHtmlText.length() > 0) { int rightTextWidth = (int)PatchedHtmlRenderer.renderHTML(rightHtmlText, g, 0, 0, Integer.MAX_VALUE, 0, - defaultFont, defaultColor, PatchedHtmlRenderer.STYLE_CLIP, false, true); + defaultFont, foregroundColor, PatchedHtmlRenderer.STYLE_CLIP, false, true); rightTextX = Math.max(iconWidth, rightTextX - rightTextWidth); // Render right text PatchedHtmlRenderer.renderHTML(rightHtmlText, g, rightTextX, textY, rightTextWidth, textY, - defaultFont, defaultColor, PatchedHtmlRenderer.STYLE_CLIP, true, selected); + defaultFont, foregroundColor, PatchedHtmlRenderer.STYLE_CLIP, true, isBestMatch && isSelected); rightTextX = Math.max(iconWidth, rightTextX - BEFORE_RIGHT_TEXT_GAP); } // Render left text if (leftHtmlText != null && leftHtmlText.length() > 0 && rightTextX > iconWidth) { // any space for left text? PatchedHtmlRenderer.renderHTML(leftHtmlText, g, iconWidth, textY, rightTextX - iconWidth, textY, - defaultFont, defaultColor, PatchedHtmlRenderer.STYLE_TRUNCATE, true, selected); + defaultFont, foregroundColor, PatchedHtmlRenderer.STYLE_TRUNCATE, true, isBestMatch && isSelected); + } + + // Render border for best match items that are not selected + if (isBestMatch && !isSelected) { + g.setColor(selectedBackgroundColor); + g.drawRect(0, 0, width - 1, height - 1); } } diff -r 81cafa2674cb -r 31225ddf6f59 java.editor/test/unit/src/org/netbeans/modules/java/editor/completion/CompletionTestBase.java --- a/java.editor/test/unit/src/org/netbeans/modules/java/editor/completion/CompletionTestBase.java Sat Feb 25 17:26:54 2012 +0100 +++ b/java.editor/test/unit/src/org/netbeans/modules/java/editor/completion/CompletionTestBase.java Sat Feb 25 13:00:55 2012 -0600 @@ -74,7 +74,7 @@ import org.netbeans.api.java.source.gen.WhitespaceIgnoringDiff; import org.netbeans.api.lexer.Language; import org.netbeans.junit.NbTestCase; -import org.netbeans.modules.editor.completion.CompletionItemComparator; +import org.netbeans.spi.editor.completion.CompletionItemComparator; import org.netbeans.modules.editor.java.JavaCompletionProvider; import org.netbeans.modules.editor.java.JavaKit; import org.netbeans.modules.editor.java.Utilities;