diff --git a/cpplite/cpplite.editor/nbproject/project.xml b/cpplite/cpplite.editor/nbproject/project.xml index db43b931d427..58f824f614ba 100644 --- a/cpplite/cpplite.editor/nbproject/project.xml +++ b/cpplite/cpplite.editor/nbproject/project.xml @@ -25,6 +25,15 @@ org.netbeans.modules.cpplite.editor + + org.netbeans.api.java.classpath + + + + 1 + 1.63 + + org.netbeans.api.templates @@ -66,7 +75,6 @@ 0 - @@ -78,6 +86,14 @@ 1.52 + + org.netbeans.modules.parsing.indexing + + + + 9.17 + + org.netbeans.modules.projectapi diff --git a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/PathRecognizerImpl.java b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/PathRecognizerImpl.java new file mode 100644 index 000000000000..2436f94fd63f --- /dev/null +++ b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/PathRecognizerImpl.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.cpplite.editor; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.modules.cpplite.editor.file.MIMETypes; +import org.netbeans.modules.parsing.spi.indexing.PathRecognizer; +import org.openide.util.lookup.ServiceProvider; + +@ServiceProvider(service=PathRecognizer.class) +public class PathRecognizerImpl extends PathRecognizer{ + + @Override + public Set getSourcePathIds() { + return Collections.singleton(ClassPath.SOURCE); + } + + @Override + public Set getLibraryPathIds() { + return Collections.emptySet(); + } + + @Override + public Set getBinaryLibraryPathIds() { + return Collections.emptySet(); + } + + @Override + public Set getMimeTypes() { + return new HashSet<>(Arrays.asList(MIMETypes.C, MIMETypes.CPP, MIMETypes.H, MIMETypes.HPP)); + } + +} diff --git a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/LanguageServerImpl.java b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/LanguageServerImpl.java index 52478eb1fae2..27a39710a86f 100644 --- a/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/LanguageServerImpl.java +++ b/cpplite/cpplite.editor/src/org/netbeans/modules/cpplite/editor/lsp/LanguageServerImpl.java @@ -66,7 +66,7 @@ public class LanguageServerImpl implements LanguageServerProvider { private static final Logger LOG = Logger.getLogger(LanguageServerImpl.class.getName()); - private Map prj2Server = new HashMap<>(); + private static final Map prj2Server = new HashMap<>(); @Override public LanguageServerDescription startServer(Lookup lookup) { @@ -117,8 +117,18 @@ public void stateChanged(ChangeEvent e) { command.add("--clang-tidy"); command.add("--completion-style=detailed"); } - Process process = new ProcessBuilder(command).redirectError(Redirect.INHERIT).start(); - return LanguageServerDescription.create(new CopyInput(process.getInputStream(), System.err), new CopyOutput(process.getOutputStream(), System.err), process); + ProcessBuilder builder = new ProcessBuilder(command); + if (LOG.isLoggable(Level.FINEST)) { + builder.redirectError(Redirect.INHERIT); + } + Process process = builder.start(); + InputStream in = process.getInputStream(); + OutputStream out = process.getOutputStream(); + if (LOG.isLoggable(Level.FINEST)) { + in = new CopyInput(in, System.err); + out = new CopyOutput(out, System.err); + } + return LanguageServerDescription.create(in, out, process); } return null; } catch (IOException ex) { diff --git a/cpplite/cpplite.project/nbproject/project.xml b/cpplite/cpplite.project/nbproject/project.xml index a2d843f88a62..993b7dcc411c 100644 --- a/cpplite/cpplite.project/nbproject/project.xml +++ b/cpplite/cpplite.project/nbproject/project.xml @@ -25,6 +25,15 @@ org.netbeans.modules.cpplite.project + + org.netbeans.api.java.classpath + + + + 1 + 1.63 + + org.netbeans.api.templates diff --git a/cpplite/cpplite.project/src/org/netbeans/modules/cpplite/project/CPPLiteProject.java b/cpplite/cpplite.project/src/org/netbeans/modules/cpplite/project/CPPLiteProject.java index 418288746b0e..e14fc4c90ae4 100644 --- a/cpplite/cpplite.project/src/org/netbeans/modules/cpplite/project/CPPLiteProject.java +++ b/cpplite/cpplite.project/src/org/netbeans/modules/cpplite/project/CPPLiteProject.java @@ -18,17 +18,24 @@ */ package org.netbeans.modules.cpplite.project; +import java.beans.PropertyChangeListener; import java.io.IOException; import java.util.concurrent.atomic.AtomicReference; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; +import javax.swing.Icon; +import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.classpath.GlobalPathRegistry; import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectInformation; import org.netbeans.api.project.ProjectManager; import org.netbeans.modules.cpplite.project.ui.customizer.CustomizerProviderImpl; +import org.netbeans.spi.java.classpath.support.ClassPathSupport; import org.netbeans.spi.project.ProjectFactory; import org.netbeans.spi.project.ProjectFactory2; import org.netbeans.spi.project.ProjectState; import org.netbeans.spi.project.ui.PrivilegedTemplates; +import org.netbeans.spi.project.ui.ProjectOpenedHook; import org.netbeans.spi.project.ui.RecommendedTemplates; import org.openide.filesystems.FileObject; import org.openide.util.ImageUtilities; @@ -92,6 +99,8 @@ private CPPLiteProject(FileObject projectDirectory) { new CPPLiteCProjectConfigurationProvider(getRootPreferences(projectDirectory)), new RecommendedTemplatesImpl(), new PrivilegedTemplatesImpl(), + new ProjectInfo(this), + new ProjectOpenHookImpl(this), this); buildConfigurations.set(BuildConfiguration.read(getBuildPreferences(projectDirectory))); } @@ -130,6 +139,10 @@ public void setCompileCommandsExecutable(String compileCommandsExecutable) { getRootPreferences(projectDirectory).put(KEY_COMPILE_COMMANDS_EXECUTABLE, compileCommandsExecutable); } + private static Icon loadProjectIcon() { + return ImageUtilities.image2Icon(ImageUtilities.loadImage("org/netbeans/modules/cpplite/project/resources/project.gif")); + } + @ServiceProvider(service=ProjectFactory.class) public static final class FactoryImpl implements ProjectFactory2 { @@ -137,7 +150,7 @@ public static final class FactoryImpl implements ProjectFactory2 { public ProjectManager.Result isProject2(FileObject projectDirectory) { Preferences prefs = getRootPreferences(projectDirectory, false); if (prefs != null && prefs.getBoolean(KEY_IS_PROJECT, false)) { - return new ProjectManager.Result(ImageUtilities.image2Icon(ImageUtilities.loadImage("org/netbeans/modules/cpplite/project/resources/project.gif"))); + return new ProjectManager.Result(loadProjectIcon()); } return null; } @@ -187,4 +200,60 @@ public String[] getPrivilegedTemplates() { return TEMPLATES; } } + + private static final class ProjectInfo implements ProjectInformation { + + private final Project prj; + + public ProjectInfo(Project prj) { + this.prj = prj; + } + + @Override + public String getName() { + return prj.getProjectDirectory().getNameExt(); + } + + @Override + public String getDisplayName() { + return prj.getProjectDirectory().getNameExt(); + } + + @Override + public Icon getIcon() { + return loadProjectIcon(); + } + + @Override + public Project getProject() { + return prj; + } + + @Override + public void addPropertyChangeListener(PropertyChangeListener listener) {} + + @Override + public void removePropertyChangeListener(PropertyChangeListener listener) {} + + } + + private static final class ProjectOpenHookImpl extends ProjectOpenedHook { + + private final ClassPath source; + + public ProjectOpenHookImpl(Project prj) { + this.source = ClassPathSupport.createClassPath(prj.getProjectDirectory()); + } + + @Override + protected void projectOpened() { + GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, new ClassPath[] {source}); + } + + @Override + protected void projectClosed() { + GlobalPathRegistry.getDefault().unregister(ClassPath.SOURCE, new ClassPath[] {source}); + } + + } } diff --git a/ide/lsp.client/nbproject/project.xml b/ide/lsp.client/nbproject/project.xml index 14c73d212bc3..f74efcdccddc 100644 --- a/ide/lsp.client/nbproject/project.xml +++ b/ide/lsp.client/nbproject/project.xml @@ -164,6 +164,15 @@ 1.72 + + org.netbeans.modules.jumpto + + + + 1 + 1.64 + + org.netbeans.modules.lexer @@ -182,6 +191,14 @@ 1.51 + + org.netbeans.modules.parsing.indexing + + + + 9.17 + + org.netbeans.modules.projectapi diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java index 27d5d18c33ad..b65920a8e793 100644 --- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/LSPBindings.java @@ -51,6 +51,7 @@ import org.eclipse.lsp4j.InitializeResult; import org.eclipse.lsp4j.ResourceOperationKind; import org.eclipse.lsp4j.ServerCapabilities; +import org.eclipse.lsp4j.SymbolCapabilities; import org.eclipse.lsp4j.SymbolKind; import org.eclipse.lsp4j.SymbolKindCapabilities; import org.eclipse.lsp4j.TextDocumentClientCapabilities; @@ -61,6 +62,7 @@ import org.eclipse.lsp4j.services.LanguageServer; import org.eclipse.lsp4j.services.TextDocumentService; import org.eclipse.lsp4j.services.WorkspaceService; +import org.eclipse.lsp4j.util.Preconditions; import org.netbeans.api.editor.mimelookup.MimeLookup; import org.netbeans.api.progress.*; import org.netbeans.api.project.FileOwnerQuery; @@ -89,6 +91,11 @@ */ public class LSPBindings { + static { + //Don't perform null checks. The servers may not adhere to the specification, and send illegal nulls. + Preconditions.enableNullChecks(false); + } + private static final RequestProcessor WORKER = new RequestProcessor(LanguageClientImpl.class.getName(), 1, false, false); private static final int DELAY = 500; @@ -110,20 +117,31 @@ public static synchronized LSPBindings getBindings(FileObject file) { break; } } + + String mimeType = FileUtil.getMIMEType(file); Project prj = FileOwnerQuery.getOwner(file); + + if (mimeType == null) { + return null; + } + + return getBindingsImpl(prj, file, mimeType, true); + } + + public static void ensureServerRunning(Project prj, String mimeType) { + getBindingsImpl(prj, prj.getProjectDirectory(), mimeType, false); + } + + public static synchronized LSPBindings getBindingsImpl(Project prj, FileObject file, String mimeType, boolean forceBindings) { FileObject dir; + if (prj == null) { dir = file.getParent(); } else { dir = prj.getProjectDirectory(); } - URI uri = dir.toURI(); - String mimeType = FileUtil.getMIMEType(file); - - if (mimeType == null) { - return null; - } + URI uri = dir.toURI(); boolean[] created = new boolean[1]; @@ -131,22 +149,18 @@ public static synchronized LSPBindings getBindings(FileObject file) { project2MimeType2Server.computeIfAbsent(uri, p -> new HashMap<>()) .computeIfAbsent(mimeType, mt -> { MimeTypeInfo mimeTypeInfo = new MimeTypeInfo(mt); - Reference prjRef = new WeakReference<>(prj); ServerRestarter restarter = () -> { synchronized (LSPBindings.class) { - Project p = prjRef.get(); - if (p != null) { - LSPBindings b = project2MimeType2Server.getOrDefault(uri, Collections.emptyMap()).remove(mimeType); - - if (b != null) { - try { - b.server.shutdown().get(); - } catch (InterruptedException | ExecutionException ex) { - LOG.log(Level.FINE, null, ex); - } - if (b.process != null) { - b.process.destroy(); - } + LSPBindings b = project2MimeType2Server.getOrDefault(uri, Collections.emptyMap()).remove(mimeType); + + if (b != null) { + try { + b.server.shutdown().get(); + } catch (InterruptedException | ExecutionException ex) { + LOG.log(Level.FINE, null, ex); + } + if (b.process != null) { + b.process.destroy(); } } } @@ -181,9 +195,12 @@ public static synchronized LSPBindings getBindings(FileObject file) { } } } - return new LSPBindings(null, null, null); + return forceBindings ? new LSPBindings(null, null, null) : null; }); + if (bindings == null) { + return null; + } if (bindings.process != null && !bindings.process.isAlive()) { //XXX: what now return null; @@ -246,6 +263,8 @@ private static InitializeResult initServer(Process p, LanguageServer server, Fil wcc.setWorkspaceEdit(new WorkspaceEditCapabilities()); wcc.getWorkspaceEdit().setDocumentChanges(true); wcc.getWorkspaceEdit().setResourceOperations(Arrays.asList(ResourceOperationKind.Create, ResourceOperationKind.Delete, ResourceOperationKind.Rename)); + SymbolCapabilities sc = new SymbolCapabilities(new SymbolKindCapabilities(Arrays.asList(SymbolKind.values()))); + wcc.setSymbol(sc); initParams.setCapabilities(new ClientCapabilities(wcc, tdcc, null)); CompletableFuture initResult = server.initialize(initParams); while (true) { @@ -261,6 +280,21 @@ private static InitializeResult initServer(Process p, LanguageServer server, Fil } } + public static Set getAllBindings() { + Set allBindings = Collections.newSetFromMap(new IdentityHashMap<>()); + + project2MimeType2Server.values() + .stream() + .flatMap(n -> n.values().stream()) + .forEach(allBindings::add); + workspace2Extension2Server.values() + .stream() + .flatMap(n -> n.values().stream()) + .forEach(allBindings::add); + + return allBindings; + } + private final LanguageServer server; private final InitializeResult initResult; private final Process process; diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/BaseSymbolProvider.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/BaseSymbolProvider.java new file mode 100644 index 000000000000..97ba03d44212 --- /dev/null +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/BaseSymbolProvider.java @@ -0,0 +1,206 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.lsp.client.bindings; + +import java.net.MalformedURLException; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.swing.Icon; +import org.eclipse.lsp4j.SymbolInformation; +import org.eclipse.lsp4j.WorkspaceSymbolParams; +import org.netbeans.api.project.FileOwnerQuery; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectInformation; +import org.netbeans.api.project.ProjectUtils; +import org.netbeans.modules.lsp.client.LSPBindings; +import org.netbeans.modules.lsp.client.Utils; +import org.netbeans.spi.jumpto.support.NameMatcher; +import org.netbeans.spi.jumpto.support.NameMatcherFactory; +import org.netbeans.spi.jumpto.type.SearchType; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.URLMapper; +import org.openide.util.Exceptions; + +/** + * + * @author lahvac + */ +public class BaseSymbolProvider { + + private static final Pattern WORD_START = Pattern.compile("(^|[^\\p{L}])(\\p{L})"); + private final AtomicBoolean cancel = new AtomicBoolean(); + private CompletableFuture> currentQuery; + + public String name() { + return "lsp-client"; + } + + public void computeSymbolNames(SearchType searchType, String searchText, BiConsumer found) { + cancel.set(false); + + List>> queries = new ArrayList<>(); + + try { + for (LSPBindings b : LSPBindings.getAllBindings()) { + if (cancel.get()) { + return ; + } + queries.add(b.getWorkspaceService().symbol(new WorkspaceSymbolParams(searchText))); + } + + NameMatcher matcher = NameMatcherFactory.createNameMatcher(searchText, searchType); + + while (!queries.isEmpty()) { + if (cancel.get()) { + return ; + } + + + try { + currentQuery = queries.remove(queries.size() - 1); + + List infos = currentQuery.get(); + + currentQuery = null; + + if (infos != null) { + for (SymbolInformation info : infos) { + if (cancel.get()) { + return ; + } + Matcher wordStartMatcher = WORD_START.matcher(info.getName()); + while (wordStartMatcher.find()) { + int nameStart = wordStartMatcher.start(2); + String namePart = info.getName().substring(nameStart); + if (matcher.accept(namePart)) { + found.accept(info, namePart); + } + } + } + } + } catch (InterruptedException ex) { + //ignore? + } catch (CancellationException ex) { + return ; + } catch (ExecutionException ex) { + LOG.log(Level.FINE, null, ex); + } + } + } finally { + if (cancel.get()) { + if (currentQuery != null) { + currentQuery.cancel(true); + } + queries.forEach(cf -> cf.cancel(true)); + } + currentQuery = null; + } + } + + private static final Logger LOG = Logger.getLogger(BaseSymbolProvider.class.getName()); + + public void cancel() { + cancel.set(true); + if (currentQuery != null) { + currentQuery.cancel(true); + } + } + + public void cleanup() { + } + + public static interface BaseSymbolDescriptor { + + public SymbolInformation getInfo(); + + public default Icon getIcon() { + return Icons.getSymbolIcon(getInfo().getKind()); + } + + public default String getSymbolName() { + return getInfo().getName(); + } + + public default String getOwnerName() { + String container = getInfo().getContainerName(); + + if (container == null || "".equals(container)) { + String uri = getInfo().getLocation().getUri(); + + container = uri.substring(uri.lastIndexOf('/') + 1); + } + + return container; + } + + public default String getProjectName() { + return getProjectInformation().map(pi -> pi.getDisplayName()).orElse(null); + } + + public default Icon getProjectIcon() { + return getProjectInformation().map(pi -> pi.getIcon()).orElse(null); + } + + //XXX: should be private: + public default Optional getProjectInformation() { + FileObject file = getFileObject(); + + if (file != null) { + Project owningProject = FileOwnerQuery.getOwner(file); + + if (owningProject != null) { + return Optional.of(ProjectUtils.getInformation(owningProject)); + } + } + + return Optional.empty(); + } + + public default FileObject getFileObject() { + try { + URI target = URI.create(getInfo().getLocation().getUri()); + + return URLMapper.findFileObject(target.toURL()); + } catch (MalformedURLException ex) { + Exceptions.printStackTrace(ex); + } + return null; + } + + public default int getOffset() { + return -1; //XXX + } + + public default void open() { + Utils.open(getInfo().getLocation().getUri(), getInfo().getLocation().getRange()); + } + + } +} diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/CustomIndexerImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/CustomIndexerImpl.java new file mode 100644 index 000000000000..7c5c98998e83 --- /dev/null +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/CustomIndexerImpl.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.lsp.client.bindings; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; +import org.netbeans.api.editor.mimelookup.MimeRegistration; +import org.netbeans.api.project.FileOwnerQuery; +import org.netbeans.api.project.Project; +import org.netbeans.modules.lsp.client.LSPBindings; +import org.netbeans.modules.parsing.spi.indexing.Context; +import org.netbeans.modules.parsing.spi.indexing.CustomIndexer; +import org.netbeans.modules.parsing.spi.indexing.CustomIndexerFactory; +import org.netbeans.modules.parsing.spi.indexing.Indexable; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.EditableProperties; +import org.openide.util.Exceptions; +import org.openide.util.RequestProcessor; + +/** + * + * @author lahvac + */ +public class CustomIndexerImpl extends CustomIndexer { + + private static final RequestProcessor WORKER = new RequestProcessor(CustomIndexerImpl.class.getName(), 1, false, false); + + @Override + protected void index(Iterable files, Context context) { + handleStoredFiles(context, props -> { + FileObject root = context.getRoot(); + for (Indexable i : files) { + FileObject file = root.getFileObject(i.getRelativePath()); + if (file != null) { + props.setProperty(i.getRelativePath(), FileUtil.getMIMEType(file)); + } + } + + Set mimeTypes = new HashSet<>(props.values()); + System.err.println("mimeTypes=" + mimeTypes); + Project prj = FileOwnerQuery.getOwner(root); + + if (prj != null) { + WORKER.post(() -> { + for (String mimeType : mimeTypes) { + LSPBindings.ensureServerRunning(prj, mimeType); + } + }); + } + }); + } + + private static final String INDEX_FILE_NAME = "index.properties"; + + private static void handleStoredFiles(Context context, Consumer handleProperties) { + EditableProperties props = new EditableProperties(true); + FileObject index = context.getIndexFolder().getFileObject(INDEX_FILE_NAME); + + if (index != null) { + try (InputStream in = index.getInputStream()) { + props.load(in); + } catch (IOException ex) { + //ignore... + } + } + + EditableProperties old = props.cloneProperties(); + + handleProperties.accept(props); + + if (!old.equals(props)) { + try { + if (index == null) { + index = context.getIndexFolder().createData(INDEX_FILE_NAME); + } + try (OutputStream out = index.getOutputStream()) { + props.store(out); + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + } + } + + @MimeRegistration(mimeType="", service=CustomIndexerFactory.class) + public static final class FactoryImpl extends CustomIndexerFactory { + + @Override + public CustomIndexer createIndexer() { + return new CustomIndexerImpl(); + } + + @Override + public boolean supportsEmbeddedIndexers() { + return true; + } + + @Override + public void filesDeleted(Iterable deleted, Context context) { + handleStoredFiles(context, props -> { + for (Indexable d : deleted) { + props.remove(d.getRelativePath()); + } + }); + } + + @Override + public void filesDirty(Iterable dirty, Context context) { + } + + @Override + public String getIndexerName() { + return "lsp-indexer"; + } + + @Override + public int getIndexVersion() { + return 0; + } + + } +} diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/Icons.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/Icons.java index 6570b06839b3..8de64f7fdbcc 100644 --- a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/Icons.java +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/Icons.java @@ -75,4 +75,8 @@ public static String getSymbolIconBase(SymbolKind symbolKind) { } return null; } + + public static Icon getSymbolIcon(SymbolKind symbolKind) { + return ImageUtilities.loadImageIcon(getSymbolIconBase(symbolKind), false); + } } diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/SymbolProviderImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/SymbolProviderImpl.java new file mode 100644 index 000000000000..250b783f91f7 --- /dev/null +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/SymbolProviderImpl.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.lsp.client.bindings; + +import javax.swing.Icon; +import org.eclipse.lsp4j.SymbolInformation; +import org.netbeans.spi.jumpto.symbol.SymbolDescriptor; +import org.netbeans.spi.jumpto.symbol.SymbolProvider; +import org.openide.filesystems.FileObject; +import org.openide.util.NbBundle.Messages; +import org.openide.util.lookup.ServiceProvider; + +/** + * + * @author lahvac + */ +@ServiceProvider(service=SymbolProvider.class) +public class SymbolProviderImpl extends BaseSymbolProvider implements SymbolProvider { + + @Override + @Messages("DN_getDisplayName=Language Server Symbol Provider") + public String getDisplayName() { + return Bundle.DN_Symbols(); + } + + @Override + public void computeSymbolNames(Context context, Result result) { + computeSymbolNames(context.getSearchType(), context.getText(), (info, simpleName) -> result.addResult(new SymbolDescriptorImpl(info, simpleName))); + } + + public static class SymbolDescriptorImpl extends SymbolDescriptor implements BaseSymbolDescriptor { + + private final SymbolInformation info; + private final String simpleName; + + public SymbolDescriptorImpl(SymbolInformation info, String simpleName) { + this.info = info; + this.simpleName = simpleName; + } + + @Override + public SymbolInformation getInfo() { + return info; + } + + @Override + public String getSimpleName() { + return simpleName; + } + + @Override + public Icon getIcon() { + return BaseSymbolDescriptor.super.getIcon(); + } + + @Override + public String getSymbolName() { + return BaseSymbolDescriptor.super.getSymbolName(); + } + + @Override + public String getOwnerName() { + return BaseSymbolDescriptor.super.getOwnerName(); + } + + @Override + public String getProjectName() { + return BaseSymbolDescriptor.super.getProjectName(); + } + + @Override + public Icon getProjectIcon() { + return BaseSymbolDescriptor.super.getProjectIcon(); + } + + @Override + public FileObject getFileObject() { + return BaseSymbolDescriptor.super.getFileObject(); + } + + @Override + public int getOffset() { + return BaseSymbolDescriptor.super.getOffset(); + } + + @Override + public void open() { + BaseSymbolDescriptor.super.open(); + } + + } +} diff --git a/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TypeProviderImpl.java b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TypeProviderImpl.java new file mode 100644 index 000000000000..498cdb35e935 --- /dev/null +++ b/ide/lsp.client/src/org/netbeans/modules/lsp/client/bindings/TypeProviderImpl.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.lsp.client.bindings; + +import java.util.EnumSet; +import java.util.Set; +import javax.swing.Icon; +import org.eclipse.lsp4j.SymbolInformation; +import org.eclipse.lsp4j.SymbolKind; +import org.netbeans.spi.jumpto.type.TypeDescriptor; +import org.netbeans.spi.jumpto.type.TypeProvider; +import org.openide.filesystems.FileObject; +import org.openide.util.NbBundle.Messages; +import org.openide.util.lookup.ServiceProvider; + +/** + * + * @author lahvac + */ +@ServiceProvider(service=TypeProvider.class) +public class TypeProviderImpl extends BaseSymbolProvider implements TypeProvider { + + private static final Set TYPE_KINDS = EnumSet.of( + SymbolKind.Class, SymbolKind.Enum, SymbolKind.Interface, + SymbolKind.Struct + ); + + @Override + @Messages("DN_TypeProviderImpl=Language Server Type Provider") + public String getDisplayName() { + return Bundle.DN_TypeProviderImpl(); + } + + @Override + public void computeTypeNames(Context context, Result result) { + computeSymbolNames(context.getSearchType(), + context.getText(), + (info, simpleName) -> { + if (TYPE_KINDS.contains(info.getKind())) { + result.addResult(new TypeDescriptorImpl(info, simpleName)); + } + }); + } + + public static class TypeDescriptorImpl extends TypeDescriptor implements BaseSymbolDescriptor { + + private final SymbolInformation info; + private final String simpleName; + + public TypeDescriptorImpl(SymbolInformation info, String simpleName) { + this.info = info; + this.simpleName = simpleName; + } + + @Override + public SymbolInformation getInfo() { + return info; + } + + @Override + public String getSimpleName() { + return simpleName; + } + + @Override + public Icon getIcon() { + return BaseSymbolDescriptor.super.getIcon(); + } + + @Override + public String getTypeName() { + return BaseSymbolDescriptor.super.getSymbolName(); + } + + @Override + public String getProjectName() { + return BaseSymbolDescriptor.super.getProjectName(); + } + + @Override + public Icon getProjectIcon() { + return BaseSymbolDescriptor.super.getProjectIcon(); + } + + @Override + public FileObject getFileObject() { + return BaseSymbolDescriptor.super.getFileObject(); + } + + @Override + public int getOffset() { + return BaseSymbolDescriptor.super.getOffset(); + } + + @Override + public void open() { + BaseSymbolDescriptor.super.open(); + } + + @Override + public String getOuterName() { + return null; + } + + @Override + public String getContextName() { + return null; + } + + } +} diff --git a/ide/parsing.lucene/nbproject/project.properties b/ide/parsing.lucene/nbproject/project.properties index 2229a955b49d..59381d5fcb9e 100644 --- a/ide/parsing.lucene/nbproject/project.properties +++ b/ide/parsing.lucene/nbproject/project.properties @@ -19,7 +19,7 @@ javac.source=1.8 javadoc.apichanges=${basedir}/apichanges.xml javac.compilerargs=-Xlint -Xlint:-serial -spec.version.base=2.46.0 +spec.version.base=2.47.0 test.config.stableBTD.includes=**/*Test.class test.config.stableBTD.excludes=\ **/LuceneIndexTest.class diff --git a/ide/parsing.lucene/nbproject/project.xml b/ide/parsing.lucene/nbproject/project.xml index d9e096f04a06..988a8457fce7 100644 --- a/ide/parsing.lucene/nbproject/project.xml +++ b/ide/parsing.lucene/nbproject/project.xml @@ -85,6 +85,7 @@ org.netbeans.modules.cnd.indexing + org.netbeans.modules.java.lsp.server org.netbeans.modules.java.source.base org.netbeans.modules.java.sourceui org.netbeans.modules.jumpto diff --git a/java/java.lsp.server/nbproject/project.xml b/java/java.lsp.server/nbproject/project.xml index 7d0d84bddd9e..1dc3a47aef80 100644 --- a/java/java.lsp.server/nbproject/project.xml +++ b/java/java.lsp.server/nbproject/project.xml @@ -25,15 +25,6 @@ org.netbeans.modules.java.lsp.server - - org.netbeans.api.annotations.common - - - - 1 - 1.34 - - org.netbeans.api.debugger @@ -69,6 +60,15 @@ 1.72 + + org.netbeans.api.annotations.common + + + + 1 + 1.36 + + org.netbeans.api.java.classpath @@ -198,6 +198,15 @@ 1.28 + + org.netbeans.modules.jumpto + + + + 1 + 1.64 + + org.netbeans.modules.lexer @@ -224,6 +233,15 @@ 9.13 + + org.netbeans.modules.parsing.lucene + + + + 2 + 2.47 + + org.netbeans.modules.projectapi @@ -242,6 +260,15 @@ 1.89 + + org.netbeans.modules.queries + + + + 1 + 1.52 + + org.netbeans.modules.refactoring.api diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Utils.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Utils.java new file mode 100644 index 000000000000..7ec9bb023a1f --- /dev/null +++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/Utils.java @@ -0,0 +1,214 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.java.lsp.server; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.LineMap; +import com.sun.source.tree.Tree; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.nio.file.Files; +import java.util.Properties; +import javax.lang.model.element.ElementKind; +import javax.swing.text.Document; +import javax.swing.text.StyledDocument; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.SymbolKind; +import org.netbeans.api.editor.document.LineDocument; +import org.netbeans.api.editor.document.LineDocumentUtils; +import org.netbeans.api.java.source.CompilationInfo; +import org.openide.cookies.EditorCookie; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.filesystems.URLMapper; +import org.openide.modules.Places; +import org.openide.text.NbDocument; +import org.openide.util.Exceptions; + +/** + * + * @author lahvac + */ +public class Utils { + + public static SymbolKind elementKind2SymbolKind(ElementKind kind) { + switch (kind) { + case PACKAGE: + return SymbolKind.Package; + case ENUM: + return SymbolKind.Enum; + case CLASS: + return SymbolKind.Class; + case ANNOTATION_TYPE: + return SymbolKind.Interface; + case INTERFACE: + return SymbolKind.Interface; + case ENUM_CONSTANT: + return SymbolKind.EnumMember; + case FIELD: + return SymbolKind.Field; //TODO: constant + case PARAMETER: + return SymbolKind.Variable; + case LOCAL_VARIABLE: + return SymbolKind.Variable; + case EXCEPTION_PARAMETER: + return SymbolKind.Variable; + case METHOD: + return SymbolKind.Method; + case CONSTRUCTOR: + return SymbolKind.Constructor; + case TYPE_PARAMETER: + return SymbolKind.TypeParameter; + case RESOURCE_VARIABLE: + return SymbolKind.Variable; + case MODULE: + return SymbolKind.Module; + case STATIC_INIT: + case INSTANCE_INIT: + case OTHER: + default: + return SymbolKind.File; //XXX: what here? + } + } + + public static Range treeRange(CompilationInfo info, Tree tree) { + long start = info.getTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), tree); + long end = info.getTrees().getSourcePositions().getEndPosition(info.getCompilationUnit(), tree); + if (end == (-1)) { + end = start; + } + return new Range(createPosition(info.getCompilationUnit(), (int) start), + createPosition(info.getCompilationUnit(), (int) end)); + } + + public static Position createPosition(CompilationUnitTree cut, int offset) { + return createPosition(cut.getLineMap(), offset); + } + + public static Position createPosition(LineMap lm, int offset) { + return new Position((int) lm.getLineNumber(offset) - 1, + (int) lm.getColumnNumber(offset) - 1); + } + + public static Position createPosition(FileObject file, int offset) { + try { + EditorCookie ec = file.getLookup().lookup(EditorCookie.class); + StyledDocument doc = ec.openDocument(); + int line = NbDocument.findLineNumber(doc, offset); + int column = NbDocument.findLineColumn(doc, offset); + + return new Position(line, column); + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + } + + public static int getOffset(Document doc, Position pos) { + return LineDocumentUtils.getLineStartFromIndex((LineDocument) doc, pos.getLine()) + pos.getCharacter(); + } + + public static String toUri(FileObject file) { + if (FileUtil.isArchiveArtifact(file)) { + //VS code cannot open jar:file: URLs, workaround: + //another workaround, should be: + //File cacheDir = Places.getCacheSubfile("java-server"); + //but that locks up VS Code, using a temp directory: + File cacheDir; + try { + cacheDir = Files.createTempDirectory("nbcode").toFile(); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + File segments = new File(cacheDir, "segments"); + Properties props = new Properties(); + + try (InputStream in = new FileInputStream(segments)) { + props.load(in); + } catch (IOException ex) { + //OK, may not exist yet + } + FileObject archive = FileUtil.getArchiveFile(file); + String archiveString = archive.toURL().toString(); + File foundSegment = null; + for (String segment : props.stringPropertyNames()) { + if (archiveString.equals(props.getProperty(segment))) { + foundSegment = new File(cacheDir, segment); + break; + } + } + if (foundSegment == null) { + int i = 0; + while (props.getProperty("s" + i) != null) + i++; + foundSegment = new File(cacheDir, "s" + i); + props.put("s" + i, archiveString); + try (OutputStream in = new FileOutputStream(segments)) { + props.store(in, ""); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + } + File cache = new File(foundSegment, FileUtil.getRelativePath(FileUtil.getArchiveRoot(archive), file)); + cache.getParentFile().mkdirs(); + try (OutputStream out = new FileOutputStream(cache)) { + out.write(file.asBytes()); + return cache.toURI().toString(); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + } + return file.toURI().toString(); + } + + public static FileObject fromUri(String uri) throws MalformedURLException { + File cacheDir = Places.getCacheSubfile("java-server"); + URI uriUri = URI.create(uri); + URI relative = cacheDir.toURI().relativize(uriUri); + if (relative != null && new File(cacheDir, relative.toString()).canRead()) { + String segmentAndPath = relative.toString(); + int slash = segmentAndPath.indexOf('/'); + String segment = segmentAndPath.substring(0, slash); + String path = segmentAndPath.substring(slash + 1); + File segments = new File(cacheDir, "segments"); + Properties props = new Properties(); + + try (InputStream in = new FileInputStream(segments)) { + props.load(in); + String archiveUri = props.getProperty(segment); + FileObject archive = URLMapper.findFileObject(URI.create(archiveUri).toURL()); + archive = archive != null ? FileUtil.getArchiveRoot(archive) : null; + FileObject file = archive != null ? archive.getFileObject(path) : null; + if (file != null) { + return file; + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + } + return URLMapper.findFileObject(URI.create(uri).toURL()); + } +} diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java index af4618178961..43a7558712f5 100644 --- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java +++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java @@ -63,6 +63,7 @@ import org.netbeans.api.project.ProjectUtils; import org.netbeans.api.project.Sources; import org.netbeans.api.project.ui.OpenProjects; +import org.netbeans.modules.java.lsp.server.Utils; import org.openide.filesystems.FileObject; import org.openide.util.Exceptions; import org.openide.util.Lookup; @@ -253,6 +254,7 @@ private InitializeResult constructInitResponse(JavaSource src) { capabilities.setDocumentHighlightProvider(true); capabilities.setReferencesProvider(true); capabilities.setExecuteCommandProvider(new ExecuteCommandOptions(Arrays.asList(JAVA_BUILD_WORKSPACE, GRAALVM_PAUSE_SCRIPT))); + capabilities.setWorkspaceSymbolProvider(true); } return new InitializeResult(capabilities); } @@ -266,7 +268,7 @@ public CompletableFuture initialize(InitializeParams init) { if (folders != null) { for (WorkspaceFolder w : folders) { try { - projectCandidates.add(TextDocumentServiceImpl.fromUri(w.getUri())); + projectCandidates.add(Utils.fromUri(w.getUri())); } catch (MalformedURLException ex) { LOG.log(Level.FINE, null, ex); } @@ -276,7 +278,7 @@ public CompletableFuture initialize(InitializeParams init) { if (root != null) { try { - projectCandidates.add(TextDocumentServiceImpl.fromUri(root)); + projectCandidates.add(Utils.fromUri(root)); } catch (MalformedURLException ex) { LOG.log(Level.FINE, null, ex); } diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java index 33d95eb8d502..bc0aab907e7c 100644 --- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java +++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java @@ -155,6 +155,7 @@ import org.netbeans.modules.java.hints.spiimpl.JavaFixImpl; import org.netbeans.modules.java.hints.spiimpl.hints.HintsInvoker; import org.netbeans.modules.java.hints.spiimpl.options.HintsSettings; +import org.netbeans.modules.java.lsp.server.Utils; import org.netbeans.modules.java.source.ElementHandleAccessor; import org.netbeans.modules.java.source.ui.ElementOpenAccessor; import org.netbeans.modules.parsing.api.ParserManager; @@ -203,10 +204,10 @@ public TextDocumentServiceImpl() { public CompletableFuture, CompletionList>> completion(CompletionParams params) { try { String uri = params.getTextDocument().getUri(); - FileObject file = fromUri(uri); + FileObject file = Utils.fromUri(uri); EditorCookie ec = file.getLookup().lookup(EditorCookie.class); Document doc = ec.openDocument(); - int caret = getOffset(doc, params.getPosition()); + int caret = Utils.getOffset(doc, params.getPosition()); JavaCompletionTask task = JavaCompletionTask.create(caret, new ItemFactoryImpl(client, uri), EnumSet.noneOf(Options.class), () -> false); ParserManager.parse(Collections.singletonList(Source.create(doc)), task); List result = task.getResults(); @@ -499,7 +500,7 @@ public CompletableFuture resolveCompletionItem(CompletionItem ci } CompletionData data = new Gson().fromJson(rawData, CompletionData.class); try { - FileObject file = fromUri(data.uri); + FileObject file = Utils.fromUri(data.uri); EditorCookie ec = file.getLookup().lookup(EditorCookie.class); Document doc = ec.openDocument(); ElementHandle handle = ElementHandleAccessor.getInstance().create(ElementKind.valueOf(data.kind), data.elementHandle); @@ -561,7 +562,7 @@ public CompletableFuture, List { cc.toPhase(JavaSource.Phase.RESOLVED); Document doc = cc.getSnapshot().getSource().getDocument(true); - int offset = getOffset(doc, params.getPosition()); + int offset = Utils.getOffset(doc, params.getPosition()); Context context = GoToSupport.resolveContext(cc, doc, offset, false, false); if (context == null) { return ; @@ -584,16 +585,16 @@ public CompletableFuture, List processImpl(CompilationInfo info, Preferences node, Document js.runUserActionTask(cc -> { cc.toPhase(JavaSource.Phase.RESOLVED); Document doc = cc.getSnapshot().getSource().getDocument(true); - int offset = getOffset(doc, params.getPosition()); + int offset = Utils.getOffset(doc, params.getPosition()); List spans = new MOHighligther().processImpl(cc, node, doc, offset); if (spans != null) { for (int[] span : spans) { - result.add(new DocumentHighlight(new Range(createPosition(cc.getCompilationUnit(), span[0]), - createPosition(cc.getCompilationUnit(), span[1])))); + result.add(new DocumentHighlight(new Range(Utils.createPosition(cc.getCompilationUnit(), span[0]), + Utils.createPosition(cc.getCompilationUnit(), span[1])))); } } }, true); @@ -783,12 +784,9 @@ private DocumentSymbol element2DocumentSymbol(CompilationInfo info, Element el) TreePath path = info.getTrees().getPath(el); if (path == null) return null; - long start = info.getTrees().getSourcePositions().getStartPosition(path.getCompilationUnit(), path.getLeaf()); - long end = info.getTrees().getSourcePositions().getEndPosition(path.getCompilationUnit(), path.getLeaf()); - if (end == (-1)) + Range range = Utils.treeRange(info, path.getLeaf()); + if (range == null) return null; - Range range = new Range(createPosition(info.getCompilationUnit(), (int) start), - createPosition(info.getCompilationUnit(), (int) end)); List children = new ArrayList<>(); for (Element c : el.getEnclosedElements()) { DocumentSymbol ds = element2DocumentSymbol(info, c); @@ -796,47 +794,16 @@ private DocumentSymbol element2DocumentSymbol(CompilationInfo info, Element el) children.add(ds); } } - return new DocumentSymbol(el.getSimpleName().toString(), elementKind2SymbolKind(el.getKind()), range, range, null, children); - } - private static SymbolKind elementKind2SymbolKind(ElementKind kind) { - switch (kind) { - case PACKAGE: - return SymbolKind.Package; - case ENUM: - return SymbolKind.Enum; - case CLASS: - return SymbolKind.Class; - case ANNOTATION_TYPE: - return SymbolKind.Interface; - case INTERFACE: - return SymbolKind.Interface; - case ENUM_CONSTANT: - return SymbolKind.EnumMember; - case FIELD: - return SymbolKind.Field; //TODO: constant - case PARAMETER: - return SymbolKind.Variable; - case LOCAL_VARIABLE: - return SymbolKind.Variable; - case EXCEPTION_PARAMETER: - return SymbolKind.Variable; - case METHOD: - return SymbolKind.Method; - case CONSTRUCTOR: - return SymbolKind.Constructor; - case TYPE_PARAMETER: - return SymbolKind.TypeParameter; - case RESOURCE_VARIABLE: - return SymbolKind.Variable; - case MODULE: - return SymbolKind.Module; - case STATIC_INIT: - case INSTANCE_INIT: - case OTHER: - default: - return SymbolKind.File; //XXX: what here? + String simpleName; + + if (el.getKind() == ElementKind.CONSTRUCTOR) { + simpleName = el.getEnclosingElement().getSimpleName().toString(); + } else { + simpleName = el.getSimpleName().toString(); } + + return new DocumentSymbol(simpleName, Utils.elementKind2SymbolKind(el.getKind()), range, range, null, children); } @Override @@ -1008,7 +975,7 @@ public CompletableFuture rename(RenameParams arg0) { @Override public void didOpen(DidOpenTextDocumentParams params) { try { - FileObject file = fromUri(params.getTextDocument().getUri()); + FileObject file = Utils.fromUri(params.getTextDocument().getUri()); EditorCookie ec = file.getLookup().lookup(EditorCookie.class); Document doc = ec.getDocument(); // the document may be not opened yet. Clash with in-memory content can happen only if @@ -1042,8 +1009,8 @@ public void didChange(DidChangeTextDocumentParams params) { NbDocument.runAtomic((StyledDocument) doc, () -> { for (TextDocumentContentChangeEvent change : params.getContentChanges()) { try { - int start = getOffset(doc, change.getRange().getStart()); - int end = getOffset(doc, change.getRange().getEnd()); + int start = Utils.getOffset(doc, change.getRange().getStart()); + int end = Utils.getOffset(doc, change.getRange().getEnd()); doc.remove(start, end - start); doc.insertString(start, change.getText(), null); } catch (BadLocationException ex) { @@ -1087,7 +1054,7 @@ private void runDiagnoticTasks(String uri) { private void computeDiags(String uri, ProduceErrors produceErrors, String keyPrefix, boolean update) { try { - FileObject file = fromUri(uri); + FileObject file = Utils.fromUri(uri); EditorCookie ec = file.getLookup().lookup(EditorCookie.class); Document doc = ec.openDocument(); ParserManager.parse(Collections.singletonList(Source.create(doc)), new UserTask() { @@ -1103,8 +1070,8 @@ public void run(ResultIterator it) throws Exception { errors = Collections.emptyList(); } for (ErrorDescription err : errors) { - Diagnostic diag = new Diagnostic(new Range(createPosition(cc.getCompilationUnit(), err.getRange().getBegin().getOffset()), - createPosition(cc.getCompilationUnit(), err.getRange().getEnd().getOffset())), + Diagnostic diag = new Diagnostic(new Range(Utils.createPosition(cc.getCompilationUnit(), err.getRange().getBegin().getOffset()), + Utils.createPosition(cc.getCompilationUnit(), err.getRange().getEnd().getOffset())), err.getDescription()); switch (err.getSeverity()) { case ERROR: diag.setSeverity(DiagnosticSeverity.Error); break; @@ -1152,7 +1119,7 @@ private JavaSource getSource(String fileUri) { Document doc = openedDocuments.get(fileUri); if (doc == null) { try { - FileObject file = fromUri(fileUri); + FileObject file = Utils.fromUri(fileUri); return JavaSource.forFileObject(file); } catch (MalformedURLException ex) { return null; @@ -1162,114 +1129,6 @@ private JavaSource getSource(String fileUri) { } } - public static Position createPosition(CompilationUnitTree cut, int offset) { - return createPosition(cut.getLineMap(), offset); - } - - public static Position createPosition(LineMap lm, int offset) { - return new Position((int) lm.getLineNumber(offset) - 1, - (int) lm.getColumnNumber(offset) - 1); - } - - public static Position createPosition(FileObject file, int offset) { - try { - EditorCookie ec = file.getLookup().lookup(EditorCookie.class); - StyledDocument doc = ec.openDocument(); - int line = NbDocument.findLineNumber(doc, offset); - int column = NbDocument.findLineColumn(doc, offset); - - return new Position(line, column); - } catch (IOException ex) { - throw new IllegalStateException(ex); - } - } - - public static int getOffset(Document doc, Position pos) { - return LineDocumentUtils.getLineStartFromIndex((LineDocument) doc, pos.getLine()) + pos.getCharacter(); - } - - private static String toUri(FileObject file) { - if (FileUtil.isArchiveArtifact(file)) { - //VS code cannot open jar:file: URLs, workaround: - //another workaround, should be: - //File cacheDir = Places.getCacheSubfile("java-server"); - //but that locks up VS Code, using a temp directory: - File cacheDir; - try { - cacheDir = Files.createTempDirectory("nbcode").toFile(); - } catch (IOException ex) { - throw new UncheckedIOException(ex); - } - File segments = new File(cacheDir, "segments"); - Properties props = new Properties(); - - try (InputStream in = new FileInputStream(segments)) { - props.load(in); - } catch (IOException ex) { - //OK, may not exist yet - } - FileObject archive = FileUtil.getArchiveFile(file); - String archiveString = archive.toURL().toString(); - File foundSegment = null; - for (String segment : props.stringPropertyNames()) { - if (archiveString.equals(props.getProperty(segment))) { - foundSegment = new File(cacheDir, segment); - break; - } - } - if (foundSegment == null) { - int i = 0; - while (props.getProperty("s" + i) != null) - i++; - foundSegment = new File(cacheDir, "s" + i); - props.put("s" + i, archiveString); - try (OutputStream in = new FileOutputStream(segments)) { - props.store(in, ""); - } catch (IOException ex) { - Exceptions.printStackTrace(ex); - } - } - File cache = new File(foundSegment, FileUtil.getRelativePath(FileUtil.getArchiveRoot(archive), file)); - cache.getParentFile().mkdirs(); - try (OutputStream out = new FileOutputStream(cache)) { - out.write(file.asBytes()); - return cache.toURI().toString(); - } catch (IOException ex) { - Exceptions.printStackTrace(ex); - } - } - return file.toURI().toString(); - } - - //TODO: move to a separate Utils class: - public static FileObject fromUri(String uri) throws MalformedURLException { - File cacheDir = Places.getCacheSubfile("java-server"); - URI uriUri = URI.create(uri); - URI relative = cacheDir.toURI().relativize(uriUri); - if (relative != null && new File(cacheDir, relative.toString()).canRead()) { - String segmentAndPath = relative.toString(); - int slash = segmentAndPath.indexOf('/'); - String segment = segmentAndPath.substring(0, slash); - String path = segmentAndPath.substring(slash + 1); - File segments = new File(cacheDir, "segments"); - Properties props = new Properties(); - - try (InputStream in = new FileInputStream(segments)) { - props.load(in); - String archiveUri = props.getProperty(segment); - FileObject archive = URLMapper.findFileObject(URI.create(archiveUri).toURL()); - archive = archive != null ? FileUtil.getArchiveRoot(archive) : null; - FileObject file = archive != null ? archive.getFileObject(path) : null; - if (file != null) { - return file; - } - } catch (IOException ex) { - Exceptions.printStackTrace(ex); - } - } - return URLMapper.findFileObject(URI.create(uri).toURL()); - } - private static List modify2TextEdits(JavaSource js, Task task) throws IOException { FileObject[] file = new FileObject[1]; LineMap[] lm = new LineMap[1]; @@ -1286,8 +1145,8 @@ private static List modify2TextEdits(JavaSource js, Task List edits = new ArrayList<>(); for (ModificationResult.Difference diff : diffs) { String newText = diff.getNewText(); - edits.add(new TextEdit(new Range(createPosition(lm[0], diff.getStartPosition().getOffset()), - createPosition(lm[0], diff.getEndPosition().getOffset())), + edits.add(new TextEdit(new Range(Utils.createPosition(lm[0], diff.getStartPosition().getOffset()), + Utils.createPosition(lm[0], diff.getEndPosition().getOffset())), newText != null ? newText : "")); } return edits; diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java index 52c395b1cdd2..8e6d849b87c9 100644 --- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java +++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java @@ -18,21 +18,51 @@ */ package org.netbeans.modules.java.lsp.server.protocol; +import com.sun.source.util.TreePath; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; import org.eclipse.lsp4j.DidChangeConfigurationParams; import org.eclipse.lsp4j.DidChangeWatchedFilesParams; import org.eclipse.lsp4j.ExecuteCommandParams; +import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.SymbolInformation; import org.eclipse.lsp4j.WorkspaceSymbolParams; import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.LanguageClientAware; import org.eclipse.lsp4j.services.WorkspaceService; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.annotations.common.NullAllowed; import org.netbeans.api.debugger.ActionsManager; import org.netbeans.api.debugger.DebuggerManager; +import org.netbeans.api.java.source.ClasspathInfo; +import org.netbeans.api.java.source.CompilationInfo; +import org.netbeans.api.java.source.ElementHandle; +import org.netbeans.api.java.source.JavaSource; +import org.netbeans.api.java.source.JavaSource.Phase; +import org.netbeans.api.java.source.SourceUtils; import org.netbeans.api.project.Project; import org.netbeans.api.project.ui.OpenProjects; +import org.netbeans.modules.java.lsp.server.Utils; +import org.netbeans.modules.java.source.ui.JavaSymbolProvider; +import org.netbeans.modules.java.source.ui.JavaSymbolProvider.ResultHandler; +import org.netbeans.modules.java.source.ui.JavaSymbolProvider.ResultHandler.Exec; +import org.netbeans.modules.java.source.usages.ClassIndexImpl; +import org.netbeans.modules.parsing.lucene.support.Queries; +import org.netbeans.spi.jumpto.type.SearchType; import org.netbeans.spi.project.ActionProvider; +import org.openide.filesystems.FileObject; +import org.openide.util.Pair; +import org.openide.util.RequestProcessor; import org.openide.util.lookup.Lookups; /** @@ -41,6 +71,8 @@ */ public final class WorkspaceServiceImpl implements WorkspaceService, LanguageClientAware { + private static final RequestProcessor WORKER = new RequestProcessor(WorkspaceServiceImpl.class.getName(), 1, false, false); + private NbCodeLanguageClient client; public WorkspaceServiceImpl() { @@ -67,8 +99,175 @@ public CompletableFuture executeCommand(ExecuteCommandParams params) { } @Override - public CompletableFuture> symbol(WorkspaceSymbolParams arg0) { - throw new UnsupportedOperationException("Not supported yet."); + public CompletableFuture> symbol(WorkspaceSymbolParams params) { + String query = params.getQuery(); + if (query.isEmpty()) { + //cannot query "all": + return CompletableFuture.completedFuture(Collections.emptyList()); + } + System.err.println("query=" + query); + boolean exact = false; + if (query.endsWith(" ")) { + query = query.substring(0, query.length() - 1); + exact = true; + } + String queryFin = query; + boolean exactFin = exact; + AtomicBoolean cancel = new AtomicBoolean(); + CompletableFuture> result = new CompletableFuture>() { + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + cancel.set(mayInterruptIfRunning); + return super.cancel(mayInterruptIfRunning); + } + }; + WORKER.post(() -> { + try { + List symbols = new ArrayList<>(); + ResultHandler handler = new ResultHandler() { + @Override + public void setHighlightText(String text) { + } + + private Map, List> type2Idents; + + @Override + public void runRoot(FileObject root, ClassIndexImpl ci, Exec exec) throws IOException, InterruptedException { + ClasspathInfo cpInfo = ClasspathInfo.create(root); + try { + type2Idents = new HashMap<>(); + exec.run(); + Map, List>> sources = new HashMap<>(); + for (Entry, List> e : type2Idents.entrySet()) { + FileObject sourceFile = SourceUtils.getFile(e.getKey(), cpInfo); + sources.computeIfAbsent(sourceFile, s -> new HashMap<>()) + .put(e.getKey(), e.getValue()); + } + if (!sources.isEmpty()) { + JavaSource.create(cpInfo, sources.keySet()) + .runUserActionTask(cc -> { + if (Phase.ELEMENTS_RESOLVED.compareTo(cc.toPhase(Phase.ELEMENTS_RESOLVED))> 0) { + return ; + } + for (Entry, List> e : sources.get(cc.getFileObject()).entrySet()) { + TypeElement te = e.getKey().resolve(cc); + + if (te == null) { + //cannot resolve + continue; + } + + for (String ident : e.getValue()) { + if (ident.equals(getSimpleName(te, null, false))) { + TreePath path = cc.getTrees().getPath(te); + + if (path != null) { + final String symbolName = te.getSimpleName().toString(); + final ElementKind kind = te.getKind(); + SymbolInformation symbol = new SymbolInformation(symbolName, Utils.elementKind2SymbolKind(kind), tree2Location(cc, path), te.getQualifiedName().toString()); + + symbol.setDeprecated(false); + symbols.add(symbol); + } + } + for (Element ne : te.getEnclosedElements()) { + if (ident.equals(getSimpleName(ne, te, false))) { + TreePath path = cc.getTrees().getPath(ne); + + if (path != null) { + final Pair name = JavaSymbolProvider.getDisplayName(ne, te); + final String symbolName = name.first() + (name.second() != null ? name.second() : ""); + final ElementKind kind = ne.getKind(); + SymbolInformation symbol = new SymbolInformation(symbolName, Utils.elementKind2SymbolKind(kind), tree2Location(cc, path), te.getQualifiedName().toString()); + + symbol.setDeprecated(false); + symbols.add(symbol); + } + } + } + } + } + }, true); + } + //TODO: handle exceptions + } finally { + type2Idents = null; + } + } + + @Override + public void handleResult(ElementHandle owner, String ident, boolean caseSensitive) { + type2Idents.computeIfAbsent(owner, s -> new ArrayList<>()).add(ident); + } + }; + JavaSymbolProvider.doComputeSymbols(getSearchType(queryFin, exactFin, false, null, null), queryFin, handler, true, cancel); + result.complete(symbols); + } catch (Throwable t) { + result.completeExceptionally(t); + } + }); + return result; + } + + private Location tree2Location(CompilationInfo info, TreePath path) { + return new Location(Utils.toUri(info.getFileObject()), + Utils.treeRange(info, path.getLeaf())); + } + + //from jumpto.Utils: + public static int containsWildCard( String text ) { + for( int i = 0; i < text.length(); i++ ) { + if ( text.charAt( i ) == '?' || text.charAt( i ) == '*' ) { // NOI18N + return i; + } + } + return -1; + } + + public static boolean isAllUpper( String text ) { + for( int i = 0; i < text.length(); i++ ) { + if ( !Character.isUpperCase( text.charAt( i ) ) ) { + return false; + } + } + + return true; + } + + public static SearchType getSearchType( + @NonNull final String text, + final boolean exact, + final boolean isCaseSensitive, + @NullAllowed final String camelCaseSeparator, + @NullAllowed final String camelCasePart) { + int wildcard = containsWildCard(text); + if (exact) { + //nameKind = isCaseSensitive ? SearchType.EXACT_NAME : SearchType.CASE_INSENSITIVE_EXACT_NAME; + return SearchType.EXACT_NAME; + } else if (wildcard != -1) { + return isCaseSensitive ? SearchType.REGEXP : SearchType.CASE_INSENSITIVE_REGEXP; + } else if ((isAllUpper(text) && text.length() > 1) || Queries.isCamelCase(text, camelCaseSeparator, camelCasePart)) { + return isCaseSensitive ? SearchType.CAMEL_CASE : SearchType.CASE_INSENSITIVE_CAMEL_CASE; + } else { + return isCaseSensitive ? SearchType.PREFIX : SearchType.CASE_INSENSITIVE_PREFIX; + } + } + + //TODO: from AsyncJavaSymbolDescriptor: + private static final String INIT = ""; //NOI18N + @NonNull + private static String getSimpleName ( + @NonNull final Element element, + @NullAllowed final Element enclosingElement, + final boolean caseSensitive) { + String result = element.getSimpleName().toString(); + if (enclosingElement != null && INIT.equals(result)) { + result = enclosingElement.getSimpleName().toString(); + } + if (!caseSensitive) { + result = result.toLowerCase(); + } + return result; } @Override diff --git a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java index 38bccfe7f9db..fda5f3f9cb53 100644 --- a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java +++ b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java @@ -84,6 +84,7 @@ import org.eclipse.lsp4j.TextEdit; import org.eclipse.lsp4j.VersionedTextDocumentIdentifier; import org.eclipse.lsp4j.WorkspaceFolder; +import org.eclipse.lsp4j.WorkspaceSymbolParams; import org.eclipse.lsp4j.jsonrpc.Launcher; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.launch.LSPLauncher; @@ -93,6 +94,7 @@ import org.netbeans.api.java.classpath.GlobalPathRegistry; import org.netbeans.api.java.source.JavaSource; import org.netbeans.api.project.Project; +import org.netbeans.api.project.ui.OpenProjects; import org.netbeans.api.sendopts.CommandLine; import org.netbeans.junit.NbTestCase; import org.netbeans.modules.java.source.BootClassPathUtil; @@ -150,7 +152,7 @@ protected void setUp() throws Exception { Class jsClass = JavaSource.class; File javaCluster = Utilities.toFile(jsClass.getProtectionDomain().getCodeSource().getLocation().toURI()).getParentFile().getParentFile(); System.setProperty("netbeans.dirs", javaCluster.getAbsolutePath()); - CacheFolderProvider.getCacheFolderForRoot(Places.getUserDirectory().toURI().toURL(), EnumSet.noneOf(CacheFolderProvider.Kind.class), CacheFolderProvider.Mode.EXISTENT); + CacheFolderProvider.getCacheFolderForRoot(Utilities.toURI(Places.getUserDirectory()).toURL(), EnumSet.noneOf(CacheFolderProvider.Kind.class), CacheFolderProvider.Mode.EXISTENT); Lookup.getDefault().lookup(ModuleInfo.class); //start the module system @@ -168,6 +170,7 @@ protected void tearDown() throws Exception { super.tearDown(); TextDocumentServiceImpl.HOOK_NOTIFICATION = null; serverThread.stop(); + OpenProjects.getDefault().close(OpenProjects.getDefault().getOpenProjects()); } List[] diags = new List[1]; @@ -214,26 +217,26 @@ public void testMain() throws Exception { serverLauncher.startListening(); LanguageServer server = serverLauncher.getRemoteProxy(); InitializeResult result = server.initialize(new InitializeParams()).get(); - server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code))); + server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code))); assertDiags(diags);//errors assertDiags(diags);//hints int hashCodeStart = code.indexOf("hashCode"); - Either, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(0, hashCodeStart + 2))).get(); + Either, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), new Position(0, hashCodeStart + 2))).get(); assertTrue(completion.isRight()); List actualItems = completion.getRight().getItems().stream().map(ci -> ci.getKind() + ":" + ci.getLabel()).collect(Collectors.toList()); assertEquals(Arrays.asList("Method:hashCode() : int"), actualItems); VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(1); - id.setUri(src.toURI().toString()); + id.setUri(toURI(src)); server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, hashCodeStart), new Position(0, hashCodeStart + "hashCode".length())), "hashCode".length(), "equ")))); assertDiags(diags, "Error:0:31-0:34");//errors assertDiags(diags, "Error:0:31-0:34");//hints - completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(0, hashCodeStart + 2))).get(); + completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), new Position(0, hashCodeStart + 2))).get(); actualItems = completion.getRight().getItems().stream().map(ci -> ci.getKind() + ":" + ci.getLabel()).collect(Collectors.toList()); if (jdk9Plus()) { assertEquals(Arrays.asList("Method:equals(Object anObject) : boolean", "Method:equalsIgnoreCase(String anotherString) : boolean"), actualItems); } int testStart = code.indexOf("test") + "equ".length() - "hashCode".length(); - completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(0, testStart + 3))).get(); + completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), new Position(0, testStart + 3))).get(); List actualCompletionItem = completion.getRight().getItems(); actualItems = actualCompletionItem.stream().map(ci -> ci.getKind() + ":" + ci.getLabel()).collect(Collectors.toList()); assertEquals(Arrays.asList("Method:test() : void"), actualItems); @@ -248,7 +251,7 @@ public void testMain() throws Exception { "Test.\n" + "\n", resolvedItem.getDocumentation().getRight().getValue()); - completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(0, 0))).get(); + completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), new Position(0, 0))).get(); actualItems = completion.getRight().getItems().stream().map(ci -> ci.getKind() + ":" + ci.getLabel()).collect(Collectors.toList()); assertTrue(actualItems.contains("Keyword:interface")); server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(0, hashCodeStart), new Position(0, hashCodeStart + "equ".length())), "equ".length(), "hashCode")))); @@ -503,11 +506,11 @@ public void logMessage(MessageParams arg0) { serverLauncher.startListening(); LanguageServer server = serverLauncher.getRemoteProxy(); InitializeResult result = server.initialize(new InitializeParams()).get(); - server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code))); + server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code))); assertDiags(diags); //errors List diagnostics = assertDiags(diags, "Warning:1:7-1:19");//hints VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(1); - id.setUri(src.toURI().toString()); + id.setUri(toURI(src)); List> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(1, 7), new Position(1, 19)), new CodeActionContext(Arrays.asList(diagnostics.get(0))))).get(); String log = codeActions.toString(); assertEquals(log, 1, codeActions.size()); @@ -597,8 +600,8 @@ public void logMessage(MessageParams arg0) { serverLauncher.startListening(); LanguageServer server = serverLauncher.getRemoteProxy(); InitializeResult result = server.initialize(new InitializeParams()).get(); - server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code))); - List> symbols = server.getTextDocumentService().documentSymbol(new DocumentSymbolParams(new TextDocumentIdentifier(src.toURI().toString()))).get(); + server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code))); + List> symbols = server.getTextDocumentService().documentSymbol(new DocumentSymbolParams(new TextDocumentIdentifier(toURI(src)))).get(); String textualSymbols = ""; String sep = ""; for (Either sym : symbols) { @@ -625,7 +628,16 @@ public void logMessage(MessageParams arg0) { " line = 7\n" + " character = 5\n" + " ]\n" + - "]:(Method:innerMethod:Range [\n" + + "]:(Constructor:Inner:Range [\n" + + " start = Position [\n" + + " line = 4\n" + + " character = 4\n" + + " ]\n" + + " end = Position [\n" + + " line = 4\n" + + " character = 4\n" + + " ]\n" + + "]:(), Method:innerMethod:Range [\n" + " start = Position [\n" + " line = 5\n" + " character = 8\n" + @@ -634,7 +646,16 @@ public void logMessage(MessageParams arg0) { " line = 6\n" + " character = 9\n" + " ]\n" + - "]:()), Field:field:Range [\n" + + "]:()), Constructor:Test:Range [\n" + + " start = Position [\n" + + " line = 0\n" + + " character = 7\n" + + " ]\n" + + " end = Position [\n" + + " line = 0\n" + + " character = 7\n" + + " ]\n" + + "]:(), Field:field:Range [\n" + " start = Position [\n" + " line = 1\n" + " character = 4\n" + @@ -715,27 +736,27 @@ public void logMessage(MessageParams arg0) { serverLauncher.startListening(); LanguageServer server = serverLauncher.getRemoteProxy(); InitializeResult result = server.initialize(new InitializeParams()).get(); - server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code))); + server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code))); Position pos = new Position(3, 30); - List definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(src.toURI().toString()), pos)).get().getLeft(); + List definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(toURI(src)), pos)).get().getLeft(); assertEquals(1, definition.size()); - assertEquals(src.toURI().toString(), definition.get(0).getUri()); + assertEquals(toURI(src), definition.get(0).getUri()); assertEquals(1, definition.get(0).getRange().getStart().getLine()); assertEquals(4, definition.get(0).getRange().getStart().getCharacter()); assertEquals(1, definition.get(0).getRange().getEnd().getLine()); assertEquals(22, definition.get(0).getRange().getEnd().getCharacter()); pos = new Position(4, 30); - definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(src.toURI().toString()), pos)).get().getLeft(); + definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(toURI(src)), pos)).get().getLeft(); assertEquals(1, definition.size()); - assertEquals(src.toURI().toString(), definition.get(0).getUri()); + assertEquals(toURI(src), definition.get(0).getUri()); assertEquals(2, definition.get(0).getRange().getStart().getLine()); assertEquals(23, definition.get(0).getRange().getStart().getCharacter()); assertEquals(2, definition.get(0).getRange().getEnd().getLine()); assertEquals(30, definition.get(0).getRange().getEnd().getCharacter()); pos = new Position(5, 22); - definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(src.toURI().toString()), pos)).get().getLeft(); + definition = server.getTextDocumentService().definition(new DefinitionParams(new TextDocumentIdentifier(toURI(src)), pos)).get().getLeft(); assertEquals(1, definition.size()); - assertEquals(otherSrc.toURI().toString(), definition.get(0).getUri()); + assertEquals(toURI(otherSrc), definition.get(0).getUri()); assertEquals(2, definition.get(0).getRange().getStart().getLine()); assertEquals(4, definition.get(0).getRange().getStart().getCharacter()); assertEquals(2, definition.get(0).getRange().getEnd().getLine()); @@ -856,10 +877,10 @@ public void logMessage(MessageParams arg0) { LanguageServer server = serverLauncher.getRemoteProxy(); InitializeResult result = server.initialize(new InitializeParams()).get(); assertTrue(result.getCapabilities().getDocumentHighlightProvider()); - server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code))); - assertHighlights(server.getTextDocumentService().documentHighlight(new DocumentHighlightParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(1, 13))).get(), + server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code))); + assertHighlights(server.getTextDocumentService().documentHighlight(new DocumentHighlightParams(new TextDocumentIdentifier(toURI(src)), new Position(1, 13))).get(), ":2:21-2:31", ":3:26-3:35", ":4:13-4:22"); - assertHighlights(server.getTextDocumentService().documentHighlight(new DocumentHighlightParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(1, 27))).get(), + assertHighlights(server.getTextDocumentService().documentHighlight(new DocumentHighlightParams(new TextDocumentIdentifier(toURI(src)), new Position(1, 27))).get(), ":1:26-1:29", ":2:12-2:15", ":3:17-3:20"); } @@ -913,16 +934,16 @@ public void logMessage(MessageParams arg0) { serverLauncher.startListening(); LanguageServer server = serverLauncher.getRemoteProxy(); InitializeParams initParams = new InitializeParams(); - initParams.setRootUri(getWorkDir().toURI().toString()); + initParams.setRootUri(toURI(getWorkDir())); InitializeResult result = server.initialize(initParams).get(); indexingComplete.await(); - server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code))); + server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code))); { - VersionedTextDocumentIdentifier id1 = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1); + VersionedTextDocumentIdentifier id1 = new VersionedTextDocumentIdentifier(toURI(src), 1); server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id1, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(2, 8), new Position(2, 8)), 0, "s.")))); - Either, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), new Position(2, 8 + "s.".length()))).get(); + Either, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), new Position(2, 8 + "s.".length()))).get(); assertTrue(completion.isRight()); Optional lengthItem = completion.getRight().getItems().stream().filter(ci -> "length() : int".equals(ci.getLabel())).findAny(); assertTrue(lengthItem.isPresent()); @@ -935,13 +956,13 @@ public void logMessage(MessageParams arg0) { } { - VersionedTextDocumentIdentifier id2 = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1); + VersionedTextDocumentIdentifier id2 = new VersionedTextDocumentIdentifier(toURI(src), 1); server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id2, Arrays.asList(new TextDocumentContentChangeEvent(new Range(new Position(1, 1), new Position(1, 1)), 0, "@java.lang.")))); Position afterJavaLang = new Position(1, 1 + "@java.lang.".length()); { - Either, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), afterJavaLang)).get(); + Either, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), afterJavaLang)).get(); assertTrue(completion.isRight()); Optional annotationItem = completion.getRight().getItems().stream().filter(ci -> "annotation".equals(ci.getLabel())).findAny(); assertTrue(annotationItem.isPresent()); @@ -954,7 +975,7 @@ public void logMessage(MessageParams arg0) { Position afterJavaLangAnnotation = new Position(1, afterJavaLang.getCharacter() + "annotation.".length()); { - Either, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), afterJavaLangAnnotation)).get(); + Either, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), afterJavaLangAnnotation)).get(); assertTrue(completion.isRight()); completion.getRight().getItems().stream().forEach(ci -> System.err.println(ci.getLabel())); Optional targetItem = completion.getRight().getItems().stream().filter(ci -> "Target".equals(ci.getLabel())).findAny(); @@ -968,7 +989,7 @@ public void logMessage(MessageParams arg0) { Position afterTarget = new Position(1, afterJavaLangAnnotation.getCharacter() + "Target(".length()); { - Either, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), afterTarget)).get(); + Either, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), afterTarget)).get(); assertTrue(completion.isRight()); completion.getRight().getItems().stream().forEach(ci -> System.err.println(ci.getLabel())); Optional methodItem = completion.getRight().getItems().stream().filter(ci -> "ElementType.METHOD".equals(ci.getLabel())).findAny(); @@ -987,7 +1008,7 @@ public void logMessage(MessageParams arg0) { { //import already exists: - Either, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(src.toURI().toString()), afterTarget)).get(); + Either, CompletionList> completion = server.getTextDocumentService().completion(new CompletionParams(new TextDocumentIdentifier(toURI(src)), afterTarget)).get(); assertTrue(completion.isRight()); completion.getRight().getItems().stream().forEach(ci -> System.err.println(ci.getLabel())); Optional methodItem = completion.getRight().getItems().stream().filter(ci -> "ElementType.METHOD".equals(ci.getLabel())).findAny(); @@ -1059,13 +1080,13 @@ public void logMessage(MessageParams arg0) { InitializeParams initParams = new InitializeParams(); initParams.setInitializationOptions(new JsonParser().parse( "{ nbcodeCapabilities: { statusBarMessageSupport : true } }").getAsJsonObject()); - initParams.setRootUri(getWorkDir().toURI().toString()); + initParams.setRootUri(toURI(getWorkDir())); InitializeResult result = server.initialize(initParams).get(); indexingComplete.await(); - server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code))); + server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code))); Diagnostic unresolvable = assertDiags(diags, "Error:2:8-2:12").get(0); - List> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(new TextDocumentIdentifier(src.toURI().toString()), unresolvable.getRange(), new CodeActionContext(Arrays.asList(unresolvable)))).get(); + List> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(new TextDocumentIdentifier(toURI(src)), unresolvable.getRange(), new CodeActionContext(Arrays.asList(unresolvable)))).get(); if (jdk9Plus()) { assertEquals(2, codeActions.size()); } @@ -1130,10 +1151,10 @@ public void logMessage(MessageParams arg0) { initParams.setRootUri(getWorkDir().toURI().toString()); InitializeResult result = server.initialize(initParams).get(); indexingComplete.await(); - server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(src.toURI().toString(), "java", 0, code))); + server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(toURI(src), "java", 0, code))); { - ReferenceParams params = new ReferenceParams(new TextDocumentIdentifier(src.toURI().toString()), + ReferenceParams params = new ReferenceParams(new TextDocumentIdentifier(toURI(src)), new Position(0, 15), new ReferenceContext(false)); @@ -1144,7 +1165,7 @@ public void logMessage(MessageParams arg0) { } { - ReferenceParams params = new ReferenceParams(new TextDocumentIdentifier(src.toURI().toString()), + ReferenceParams params = new ReferenceParams(new TextDocumentIdentifier(toURI(src)), new Position(0, 15), new ReferenceContext(true)); @@ -1188,6 +1209,64 @@ public void logMessage(MessageParams arg0) { } } + public void testWorkspaceSymbols() throws Exception { + File src = new File(getWorkDir(), "Test.java"); + src.getParentFile().mkdirs(); + try (Writer w = new FileWriter(new File(src.getParentFile(), ".test-project"))) {} + String code = "public class Test {\n" + + " public static class TestNested {}\n" + + " public static void testMethod() {}\n" + + "}\n"; + try (Writer w = new FileWriter(src)) { + w.write(code); + } + CountDownLatch indexingComplete = new CountDownLatch(1); + Launcher serverLauncher = LSPLauncher.createClientLauncher(new LanguageClient() { + @Override + public void telemetryEvent(Object arg0) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void publishDiagnostics(PublishDiagnosticsParams params) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void showMessage(MessageParams params) { + if (Server.INDEXING_COMPLETED.equals(params.getMessage())) { + indexingComplete.countDown(); + } else { + throw new UnsupportedOperationException("Unexpected message."); + } + } + + @Override + public CompletableFuture showMessageRequest(ShowMessageRequestParams arg0) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void logMessage(MessageParams arg0) { + throw new UnsupportedOperationException("Not supported yet."); + } + }, client.getInputStream(), client.getOutputStream()); + serverLauncher.startListening(); + LanguageServer server = serverLauncher.getRemoteProxy(); + InitializeParams initParams = new InitializeParams(); + initParams.setRootUri(toURI(getWorkDir())); + InitializeResult result = server.initialize(initParams).get(); + indexingComplete.await(); + List symbols = server.getWorkspaceService().symbol(new WorkspaceSymbolParams("Tes")).get(); + List actual = symbols.stream().map(si -> si.getKind() + ":" + si.getName() + ":" + si.getContainerName() + ":" + si.getDeprecated() + ":" + toString(si.getLocation())).collect(Collectors.toList()); + assertEquals(Arrays.asList("Class:Test:Test:false:Test.java:0:0-3:1", + "Constructor:():Test:false:Test.java:0:7-0:7", + "Method:():Test:false:Test.java:2:4-2:38", + "Class:TestNested:Test.TestNested:false:Test.java:1:4-1:37", + "Constructor:():Test.TestNested:false:Test.java:1:18-1:18"), + actual); + } + private String toString(Location location) { String path = location.getUri(); String simpleName = path.substring(path.lastIndexOf('/') + 1); @@ -1207,6 +1286,10 @@ private void assertHighlights(List highlights, Stri stringHighlights); } + private String toURI(File f) { + return Utilities.toURI(f).toString(); + } + private static boolean jdk9Plus() { String version = System.getProperty("java.version"); if (version == null || version.startsWith("1.")) { diff --git a/java/java.lsp.server/vscode/package.json b/java/java.lsp.server/vscode/package.json index 963da4d9a9a6..77364b962379 100644 --- a/java/java.lsp.server/vscode/package.json +++ b/java/java.lsp.server/vscode/package.json @@ -24,6 +24,7 @@ }, "activationEvents": [ "onLanguage:java", + "workspaceContains:**/*.java", "workspaceContains:pom.xml", "workspaceContains:build.gradle", "onDebug" diff --git a/java/java.sourceui/src/org/netbeans/modules/java/source/ui/JavaSymbolProvider.java b/java/java.sourceui/src/org/netbeans/modules/java/source/ui/JavaSymbolProvider.java index 2498cc503ba4..8a568566d304 100644 --- a/java/java.sourceui/src/org/netbeans/modules/java/source/ui/JavaSymbolProvider.java +++ b/java/java.sourceui/src/org/netbeans/modules/java/source/ui/JavaSymbolProvider.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; import javax.lang.model.element.Element; @@ -82,7 +83,7 @@ public class JavaSymbolProvider implements SymbolProvider { private static final String CAPTURED_WILDCARD = ""; //NOI18N private static final String UNKNOWN = ""; //NOI18N - private volatile boolean canceled; + private final AtomicBoolean canceled = new AtomicBoolean(); @Override public String name() { @@ -117,110 +118,155 @@ public void computeSymbolNames(final Context context, final Result result) { final Cache cache = scanInProgress ? Cache.create(textToSearch, st) : null; - String prefix = null; - final int dotIndex = textToSearch.lastIndexOf('.'); //NOI18N - if (dotIndex > 0 && dotIndex != textToSearch.length()-1) { - prefix = textToSearch.substring(0, dotIndex); - textToSearch = textToSearch.substring(dotIndex+1); - } - final String textToHighLight = textToSearch; - ClassIndex.NameKind _kind; - boolean _caseSensitive; - switch (st) { - case PREFIX: - _kind = ClassIndex.NameKind.PREFIX; - _caseSensitive = true; - break; - case REGEXP: - _kind = ClassIndex.NameKind.REGEXP; - textToSearch = NameMatcherFactory.wildcardsToRegexp( - removeNonJavaChars(textToSearch), - true); - _caseSensitive = true; - break; - case CAMEL_CASE: - _kind = ClassIndex.NameKind.CAMEL_CASE; - _caseSensitive = true; - break; - case CASE_INSENSITIVE_CAMEL_CASE: - _kind = ClassIndex.NameKind.CAMEL_CASE_INSENSITIVE; - _caseSensitive = false; - break; - case EXACT_NAME: - _kind = ClassIndex.NameKind.SIMPLE_NAME; - _caseSensitive = true; - break; - case CASE_INSENSITIVE_PREFIX: - _kind = ClassIndex.NameKind.CASE_INSENSITIVE_PREFIX; - _caseSensitive = false; - break; - case CASE_INSENSITIVE_EXACT_NAME: - _kind = ClassIndex.NameKind.CASE_INSENSITIVE_REGEXP; - _caseSensitive = false; - break; - case CASE_INSENSITIVE_REGEXP: - _kind = ClassIndex.NameKind.CASE_INSENSITIVE_REGEXP; - textToSearch = NameMatcherFactory.wildcardsToRegexp( - removeNonJavaChars(textToSearch), - true); - _caseSensitive = false; - break; - default: - throw new IllegalArgumentException(); - } - final String ident = textToSearch; - final ClassIndex.NameKind kind = _kind; - final boolean caseSensitive = _caseSensitive; - final Pair restriction; - if (prefix != null) { - restriction = compileName(prefix,caseSensitive); - result.setHighlightText(textToHighLight); - } else { - restriction = null; - } - try { - final ClassIndexManager manager = ClassIndexManager.getDefault(); - - Collection roots = QuerySupport.findRoots( - (Project)null, - Collections.singleton(ClassPath.SOURCE), - Collections.emptySet(), - Collections.emptySet()); - - final Set rootUrls = new HashSet<>(); - for(FileObject root : roots) { - if (canceled) { - return; + doComputeSymbols(st, textToSearch, new ResultHandler() { + private FileObject root; + private ProjectInformation projectInfo; + private ClassIndexImpl ci; + @Override + public void setHighlightText(String text) { + result.setHighlightText(text); + } + + @Override + public void runRoot(FileObject root, ClassIndexImpl ci, Exec exec) throws IOException, InterruptedException { + try { + Project project = FileOwnerQuery.getOwner(root); + + this.root = root; + this.projectInfo = project == null ? + null : + project.getLookup().lookup(ProjectInformation.class); //Intentionally does not use ProjectUtils.getInformation() it does project icon annotation which is expensive + this.ci = ci; + exec.run(); + } finally { + this.root = null; + this.projectInfo = null; + this.ci = null; } - rootUrls.add(root.toURL()); } - if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.log(Level.FINE, "Querying following roots:"); //NOI18N - for (URL url : rootUrls) { - LOGGER.log(Level.FINE, " {0}", url); //NOI18N + @Override + public void handleResult(ElementHandle owner, String ident, boolean caseSensitive) { + final AsyncJavaSymbolDescriptor d = new AsyncJavaSymbolDescriptor( + projectInfo, + root, + ci, + owner, + ident, + caseSensitive); + result.addResult(d); + if (cache != null) { + cache.offer(d); } - LOGGER.log(Level.FINE, "-------------------------"); //NOI18N } - //Perform all queries in single op - IndexManager.priorityAccess(new IndexManager.Action() { - @Override - public Void run() throws IOException, InterruptedException { - for (URL url : rootUrls) { - if (canceled) { - return null; - } - final FileObject root = URLMapper.findFileObject(url); - if (root == null) { - continue; - } - - final Project project = FileOwnerQuery.getOwner(root); - final ProjectInformation projectInfo = project == null ? - null : - project.getLookup().lookup(ProjectInformation.class); //Intentionally does not use ProjectUtils.getInformation() it does project icon annotation which is expensive - final ClassIndexImpl impl = manager.getUsagesQuery(root.toURL(), true); - if (impl != null) { + }, true, canceled); + } finally { + clearCancel(); + } + } + + public static void doComputeSymbols(SearchType st, String textToSearch, ResultHandler handler, boolean async, AtomicBoolean canceled) { + String prefix = null; + final int dotIndex = textToSearch.lastIndexOf('.'); //NOI18N + if (dotIndex > 0 && dotIndex != textToSearch.length()-1) { + prefix = textToSearch.substring(0, dotIndex); + textToSearch = textToSearch.substring(dotIndex+1); + } + final String textToHighLight = textToSearch; + ClassIndex.NameKind _kind; + boolean _caseSensitive; + switch (st) { + case PREFIX: + _kind = ClassIndex.NameKind.PREFIX; + _caseSensitive = true; + break; + case REGEXP: + _kind = ClassIndex.NameKind.REGEXP; + textToSearch = NameMatcherFactory.wildcardsToRegexp( + removeNonJavaChars(textToSearch), + true); + _caseSensitive = true; + break; + case CAMEL_CASE: + _kind = ClassIndex.NameKind.CAMEL_CASE; + _caseSensitive = true; + break; + case CASE_INSENSITIVE_CAMEL_CASE: + _kind = ClassIndex.NameKind.CAMEL_CASE_INSENSITIVE; + _caseSensitive = false; + break; + case EXACT_NAME: + _kind = ClassIndex.NameKind.SIMPLE_NAME; + _caseSensitive = true; + break; + case CASE_INSENSITIVE_PREFIX: + _kind = ClassIndex.NameKind.CASE_INSENSITIVE_PREFIX; + _caseSensitive = false; + break; + case CASE_INSENSITIVE_EXACT_NAME: + _kind = ClassIndex.NameKind.CASE_INSENSITIVE_REGEXP; + _caseSensitive = false; + break; + case CASE_INSENSITIVE_REGEXP: + _kind = ClassIndex.NameKind.CASE_INSENSITIVE_REGEXP; + textToSearch = NameMatcherFactory.wildcardsToRegexp( + removeNonJavaChars(textToSearch), + true); + _caseSensitive = false; + break; + default: + throw new IllegalArgumentException(); + } + final String ident = textToSearch; + final ClassIndex.NameKind kind = _kind; + final boolean caseSensitive = _caseSensitive; + final Pair restriction; + if (prefix != null) { + restriction = compileName(prefix,caseSensitive); + handler.setHighlightText(textToHighLight); + } else { + restriction = null; + } + try { + final ClassIndexManager manager = ClassIndexManager.getDefault(); + + Collection roots = QuerySupport.findRoots( + (Project)null, + Collections.singleton(ClassPath.SOURCE), + Collections.emptySet(), + Collections.emptySet()); + + final Set rootUrls = new HashSet<>(); + for(FileObject root : roots) { + if (canceled.get()) { + return; + } + rootUrls.add(root.toURL()); + } + + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, "Querying following roots:"); //NOI18N + for (URL url : rootUrls) { + LOGGER.log(Level.FINE, " {0}", url); //NOI18N + } + LOGGER.log(Level.FINE, "-------------------------"); //NOI18N + } + //Perform all queries in single op + IndexManager.priorityAccess(new IndexManager.Action() { + @Override + public Void run() throws IOException, InterruptedException { + for (URL url : rootUrls) { + if (canceled.get()) { + return null; + } + final FileObject root = URLMapper.findFileObject(url); + if (root == null) { + continue; + } + + final ClassIndexImpl impl = manager.getUsagesQuery(root.toURL(), true); + if (impl != null) { + handler.runRoot(root, impl, () -> { final Map,Set> r = new HashMap<>(); impl.getDeclaredElements(ident, kind, DocumentUtil.typeElementConvertor(),r); if (!r.isEmpty()) { @@ -228,34 +274,30 @@ public Void run() throws IOException, InterruptedException { final ElementHandle owner = p.getKey(); for (String symbol : p.getValue()) { if (matchesRestrictions(owner.getQualifiedName(), symbol, restriction, caseSensitive)) { - final AsyncJavaSymbolDescriptor d = new AsyncJavaSymbolDescriptor( - projectInfo, - root, - impl, - owner, - symbol, - caseSensitive); - result.addResult(d); - if (cache != null) { - cache.offer(d); - } + handler.handleResult(owner, symbol, caseSensitive); } } } } - } + }); } - return null; } - }); - } catch (IOException ioe) { - Exceptions.printStackTrace(ioe); - } - catch (InterruptedException ie) { - return; - } - } finally { - clearCancel(); + return null; + } + }); + } catch (IOException ioe) { + Exceptions.printStackTrace(ioe); + } catch (InterruptedException ie) { + //ignore + } + } + + public interface ResultHandler { + public void setHighlightText(String text); + public void runRoot(FileObject root, ClassIndexImpl ci, Exec exec) throws IOException, InterruptedException; + public void handleResult(@NonNull ElementHandle owner, @NonNull String ident, boolean caseSensitive); + public interface Exec { + public void run() throws IOException, InterruptedException; } } @@ -317,7 +359,7 @@ private static boolean containsWildCard(String text) { } @NonNull - static Pair getDisplayName ( + public static Pair getDisplayName ( @NonNull final Element e, @NonNull final Element enclosingElement) { assert e != null; @@ -483,7 +525,7 @@ private static String removeNonJavaChars(String text) { @Override public void cancel() { - canceled = true; + canceled.set(true); } @Override @@ -493,7 +535,7 @@ public void cleanup() { } private void clearCancel() { - canceled = false; + canceled.set(false); } private static final class Cache { @@ -519,7 +561,7 @@ void populateResult(@NonNull final Result result) { } } - void offer(@NonNull final AsyncJavaSymbolDescriptor d) { + void offer(@NonNull final SymbolDescriptor d) { descriptors.add(d); }