--- a/editor.completion/nbproject/project.properties +++ a/editor.completion/nbproject/project.properties @@ -44,4 +44,4 @@ javac.source=1.7 javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml -spec.version.base=1.44.0 +spec.version.base=1.45.0 --- a/editor.completion/nbproject/project.xml +++ a/editor.completion/nbproject/project.xml @@ -112,6 +112,15 @@ + org.netbeans.modules.lexer + + + + 2 + 1.63 + + + org.netbeans.modules.sampler --- a/editor.completion/src/org/netbeans/modules/editor/completion/CompletionImpl.java +++ a/editor.completion/src/org/netbeans/modules/editor/completion/CompletionImpl.java @@ -54,8 +54,11 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; @@ -75,6 +78,8 @@ import org.netbeans.api.editor.mimelookup.MimeLookup; import org.netbeans.api.editor.mimelookup.MimePath; import org.netbeans.api.editor.settings.KeyBindingSettings; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenSequence; import org.netbeans.editor.BaseDocument; import org.netbeans.editor.BaseKit; import org.netbeans.editor.GuardedDocument; @@ -189,7 +194,7 @@ /* 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. */ + /** Mapping of mime-path to array of providers. Changed in AWT only. */ private HashMap providersCache = new HashMap(); /** @@ -539,8 +544,15 @@ } private boolean ensureActiveProviders() { - if (activeProviders != null) - return true; + if (activeProviders != null) { + String mime = getMimePath(getActiveComponent()); + if (mime == null) { + return false; + } + if (mime.equals(currentMimePath)) { + return true; + } + } JTextComponent component = getActiveComponent(); activeProviders = (component != null) ? getCompletionProvidersForComponent(component, false) @@ -574,35 +586,103 @@ int docDelay = CompletionSettings.getInstance(getActiveComponent()).documentationAutoPopupDelay(); docAutoPopupTimer.setInitialDelay(docDelay); - docAutoPopupTimer.restart(); + docAutoPopupTimer.restart(); } + /** + * Gives MimePath of the caret position as String + * @param component + * @return + */ + private String getMimePath(JTextComponent component) { + final int offset = component.getCaretPosition(); + final MimePath[] mimePathR = new MimePath[1]; + final Document doc = component.getDocument(); + + doc.render(new Runnable() { + @Override + public void run() { + List> seqs = TokenHierarchy.get(doc).embeddedTokenSequences(offset, true); + TokenSequence seq; + + if (seqs.isEmpty()) { + seq = TokenHierarchy.get(doc).tokenSequence(); + } else { + seq = seqs.get(seqs.size() - 1); + } + + if (seq != null) { + mimePathR[0] = MimePath.parse(seq.languagePath().mimePath()); + } + } + }); + if (mimePathR[0] != null) { + return mimePathR[0].getPath(); + } + // original mimeType code + Object mimeTypeObj = DocumentUtilities.getMimeType(doc); //NOI18N + String mimeType; + + if (mimeTypeObj instanceof String) { + mimeType = (String) mimeTypeObj; + } else { + BaseKit kit = Utilities.getKit(component); + + if (kit == null) { + return null; + } + + mimeType = kit.getContentType(); + } + return mimeType; + } + + private String currentMimePath; + + /** + * Gets a complete MIME path for the given offset in the document + * @param doc the document + * @param offset point of interest + * @return MimePath + */ + static MimePath getMimePath(final Document doc, final int offset) { + final MimePath[] mimePathR = new MimePath[1]; + doc.render(new Runnable() { + @Override + public void run() { + List> seqs = TokenHierarchy.get(doc).embeddedTokenSequences(offset, true); + TokenSequence seq = seqs.isEmpty() ? null : seqs.get(seqs.size() - 1); + seq = seq == null ? TokenHierarchy.get(doc).tokenSequence() : seq; + mimePathR[0] = seq == null ? MimePath.parse(DocumentUtilities.getMimeType(doc)) : MimePath.parse(seq.languagePath().mimePath()); + } + }); + return mimePathR[0]; + } + + /** + * Has side effects ! + * @param component + * @param asyncWarmUp + * @return + */ private CompletionProvider[] getCompletionProvidersForComponent(JTextComponent component, boolean asyncWarmUp) { assert (SwingUtilities.isEventDispatchThread()); if (component == null) return null; - Object mimeTypeObj = component.getDocument().getProperty("mimeType"); //NOI18N - String mimeType; - - if (mimeTypeObj instanceof String) - mimeType = (String) mimeTypeObj; - else { - BaseKit kit = Utilities.getKit(component); - - if (kit == null) { - return new CompletionProvider[0]; - } - - mimeType = kit.getContentType(); + String mimePathString = getMimePath(component); + + if (mimePathString == null) { + return null; } - - if (providersCache.containsKey(mimeType)) - return providersCache.get(mimeType); + if (providersCache.containsKey(mimePathString)) { + currentMimePath = mimePathString; + return providersCache.get(mimePathString); + } if (asyncWarmUpTask != null) { - if (asyncWarmUp && mimeType != null && mimeType.equals(asyncWarmUpMimeType)) + if (asyncWarmUp && mimePathString != null && mimePathString.equals(asyncWarmUpMimeType)) return null; if (!asyncWarmUpTask.cancel()) { asyncWarmUpTask.waitFinished(); @@ -610,9 +690,10 @@ asyncWarmUpTask = null; asyncWarmUpMimeType = null; } - final Lookup lookup = MimeLookup.getLookup(MimePath.get(mimeType)); + MimePath path = MimePath.parse(mimePathString); if (asyncWarmUp) { - asyncWarmUpMimeType = mimeType; + final Lookup lookup = MimeLookup.getLookup(path); + asyncWarmUpMimeType = mimePathString; asyncWarmUpTask = RequestProcessor.getDefault().post(new Runnable() { @Override public void run() { @@ -621,10 +702,37 @@ }); return null; } - Collection col = lookup.lookupAll(CompletionProvider.class); - int size = col.size(); - CompletionProvider[] ret = size == 0 ? null : col.toArray(new CompletionProvider[size]); - providersCache.put(mimeType, ret); + // Note 1: + // it's not possible to compare instances of CompletionProvider; each MimeLookup has its own instances, so if e.g. MyLookup + // is registered for text/html then MimeLookup for text/x-jsp/text/html and MimeLookup for text/html produce a *different* + // instances of the provider. + // the registered classes could be used as key instead BUT there would be no way how to disambiguate the registration, IF + // it was really needed for whatever reason. So I decided to match the providers based on Lookup.Item.getID(), which evaluates + // (usually) to the registration filename, which the developer can alter, if for some reason the provider is needed. + + List allProviders = new ArrayList<>(); + Lookup lookup; + Set seenProviders = new HashSet<>(); + + for (int pref = path.size(); pref >= 1; pref--) { + lookup = MimeLookup.getLookup(path.getPrefix(pref)); + Collection allItems = lookup.lookupResult(CompletionProvider.class).allItems(); + for (Lookup.Item i : allItems) { + String id = i.getId(); + int lastSlash = id.lastIndexOf('/'); // NOI18N + if (lastSlash > 0) { + String fname = id.substring(lastSlash + 1, id.length()); + if (!seenProviders.add(fname)) { + // the provider has been already seen in this list; do not add it. + continue; + } + } + allProviders.add((CompletionProvider)i.getInstance()); + } + } + currentMimePath = mimePathString; + CompletionProvider[] ret; + providersCache.put(mimePathString, ret = allProviders.toArray(new CompletionProvider[allProviders.size()])); return ret; }