Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use StructureProvider to populate navigator #7483

Merged
merged 6 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions ide/lsp.client/nbproject/project.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@
<specification-version>1.9</specification-version>
</run-dependency>
</dependency>
<dependency>
<code-name-base>org.netbeans.api.lsp</code-name-base>
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
<specification-version>1.28</specification-version>
</run-dependency>
</dependency>
<dependency>
<code-name-base>org.netbeans.api.progress</code-name-base>
<build-prerequisite/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
* 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.awt.BorderLayout;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.logging.Logger;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import org.netbeans.spi.navigator.NavigatorPanel;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.view.BeanTreeView;
import org.openide.filesystems.FileObject;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.NbBundle.Messages;

abstract class AbstractNavigatorPanel<K> extends Children.Keys<K> implements NavigatorPanel, LookupListener {
static final Logger LOG = Logger.getLogger(AbstractNavigatorPanel.class.getName());

private final ExplorerManager manager;
private View view;
private Lookup.Result<FileObject> result;
private FileObject file;

public AbstractNavigatorPanel() {
manager = new ExplorerManager();
manager.setRootContext(new AbstractNode(this));
}

abstract void addBackgroundTask(FileObject fo);
abstract void removeBackgroundTask(FileObject fo);
abstract Node[] createNodes(FileObject fo, K sym);

@Override
protected final Node[] createNodes(K key) {
return createNodes(file, key);
}

final boolean isCurrentFile(FileObject fo) {
return Objects.equals(file, fo);
}

final void expandAll() {
SwingUtilities.invokeLater(view::expandAll);
}

@Override
@Messages("DN_Symbols=Symbols")
public final String getDisplayName() {
return Bundle.DN_Symbols();
}

@Override
public final String getDisplayHint() {
return "symbols";
}

@Override
public final JComponent getComponent() {
if (view == null) {
view = new View();
}
return view;
}

@Override
public final void panelActivated(Lookup context) {
result = context.lookupResult(FileObject.class);
result.addLookupListener(this);
updateFile();
}

@Override
public final void panelDeactivated() {
result.removeLookupListener(this);
result = null;
updateFile();
}

private void updateFile() {
if (file != null) {
removeBackgroundTask(file);
setKeys(Collections.emptyList());
file = null;
}
Collection<? extends FileObject> files = result != null ? result.allInstances() : Collections.emptyList();
file = files.isEmpty() ? null : files.iterator().next();
if (file != null) {
addBackgroundTask(file);
}
}

@Override
public final Lookup getLookup() {
return Lookup.EMPTY;
}

@Override
public final void resultChanged(LookupEvent arg0) {
updateFile();
}

private class View extends JPanel implements ExplorerManager.Provider {

private final BeanTreeView internalView;

public View() {
setLayout(new BorderLayout());
this.internalView = new BeanTreeView();
add(internalView, BorderLayout.CENTER);

internalView.setRootVisible(false);
}

@Override
public ExplorerManager getExplorerManager() {
return manager;
}

public void expandAll() {
boolean scrollsOnExpand = internalView.getScrollsOnExpand();
internalView.setScrollsOnExpand(false);
internalView.expandAll();
internalView.setScrollsOnExpand(scrollsOnExpand);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public static Icon getCompletionIcon(CompletionItemKind completionKind) {

}

public static String getSymbolIconBase(SymbolKind symbolKind) {
public static String getSymbolIconBase(Enum<?> symbolKind) {
if (symbolKind == null) {
return ICON_BASE + "empty.png";
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* 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.Collection;
import java.util.Collections;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.modules.lsp.client.LSPBindings;
import org.netbeans.spi.lsp.StructureProvider;
import org.netbeans.spi.navigator.NavigatorPanel;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.URLMapper;
import org.openide.util.lookup.ServiceProvider;

@ServiceProvider(service = NavigatorPanel.DynamicRegistration.class)
public final class LspNavigatorPanelDynamicRegistration implements NavigatorPanel.DynamicRegistration {

@Override
public Collection<? extends NavigatorPanel> panelsFor(URI uri) {
try {
FileObject file = URLMapper.findFileObject(uri.toURL());
if (file != null) {
LSPBindings bindings = LSPBindings.getBindings(file);
if (bindings != null) {
return Collections.singletonList(NavigatorPanelImpl.INSTANCE);
} else {
for (StructureProvider sp : MimeLookup.getLookup(file.getMIMEType()).lookupAll(StructureProvider.class)) {
if (sp != null) {
return Collections.singletonList(LspStructureNavigatorPanel.INSTANCE);
}
}
}
}
} catch (MalformedURLException ex) {
//ignore
}
return Collections.emptyList();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* 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.awt.BorderLayout;
import java.awt.EventQueue;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.text.StyledDocument;
import org.netbeans.api.actions.Openable;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.api.lsp.StructureElement;
import org.netbeans.spi.lsp.StructureProvider;
import org.netbeans.spi.navigator.NavigatorPanel;
import org.openide.awt.Actions;
import org.openide.cookies.EditorCookie;
import org.openide.cookies.LineCookie;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.view.BeanTreeView;
import org.openide.filesystems.FileObject;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.text.Line;
import org.openide.text.NbDocument;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;

@NbBundle.Messages(value = {
"# {0} - name of the file",
"CTL_StructureForFile=Structure for {0}",
"# {0} - mime type",
"CTL_StructureForMimeType=Structure for {0}"
})
final class LspStructureNavigatorPanel extends AbstractNavigatorPanel<StructureElement> {
static final LspStructureNavigatorPanel INSTANCE = new LspStructureNavigatorPanel();
private static final RequestProcessor BACKGROUND = new RequestProcessor(LspStructureNavigatorPanel.class);
private RequestProcessor.Task reparseTask;

@Override
void addBackgroundTask(FileObject fo) {
removeBackgroundTask(fo);
reparseTask = BACKGROUND.post(() -> refreshStructure(fo));
}

@Override
void removeBackgroundTask(FileObject fo) {
if (reparseTask != null) {
reparseTask.cancel();
}
}

void refreshStructure(FileObject fo) {
LOG.log(Level.INFO, "panelActivated: {0}", fo);
EditorCookie ec = fo.getLookup().lookup(EditorCookie.class);
if (ec != null) {
StyledDocument doc = ec.getDocument();
if (doc != null) {
for (StructureProvider sp : MimeLookup.getLookup(fo.getMIMEType()).lookupAll(StructureProvider.class)) {
setKeys(sp.getStructure(doc));
return;
}
}
}
}

@Override
Node[] createNodes(FileObject fo, StructureElement key) {
return new Node[]{new StructureElementNode(key, fo)};
}
private static final class StructureElementChildren extends Children.Keys<StructureElement> {

private final FileObject fo;

StructureElementChildren(FileObject fo) {
this.fo = fo;
}

static Children childrenFor(Collection<? extends StructureElement> elements, FileObject fo) {
if (elements == null) {
return Children.LEAF;
} else {
StructureElementChildren ch = new StructureElementChildren(fo);
ch.setKeys(elements);
return ch;
}
}

@Override
protected Node[] createNodes(StructureElement key) {
return new Node[]{new StructureElementNode(key, fo)};
}
}

private static final class StructureElementNode extends AbstractNode {

private final FileObject fo;

StructureElementNode(StructureElement e, FileObject fo) {
this(e, fo, new InstanceContent());
}

private StructureElementNode(StructureElement e, FileObject fo, InstanceContent ic) {
super(StructureElementChildren.childrenFor(e.getChildren(), fo), new AbstractLookup(ic));
this.fo = fo;
setName(e.getName());
setShortDescription(e.getDetail());
setIconBaseWithExtension(Icons.getSymbolIconBase(e.getKind()));
Openable open = () -> {
EditorCookie ec = fo.getLookup().lookup(EditorCookie.class);
if (ec != null) {
StyledDocument doc = ec.getDocument();
if (doc != null) {
int lineNumber = NbDocument.findLineNumber(doc, e.getSelectionStartOffset());
LineCookie lines = fo.getLookup().lookup(LineCookie.class);
if (lines != null) {
Line at = lines.getLineSet().getOriginal(lineNumber);
if (at != null) {
at.show(Line.ShowOpenType.OPEN, Line.ShowVisibilityType.FOCUS);
}
}
}
}
};
ic.add(open);
}

@Override
public Action getPreferredAction() {
return Actions.forID("System", "org.openide.actions.OpenAction");
}
}

}
Loading
Loading