mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-04-29 19:45:01 +02:00
Fix for 191151, empty views for non-indexed files.
This commit is contained in:
parent
ca40733bf3
commit
e3e0cf82a4
13 changed files with 245 additions and 34 deletions
|
@ -620,6 +620,9 @@ public class CoreModelUtil {
|
|||
return (ITranslationUnit) tu;
|
||||
}
|
||||
} catch (CModelException e) {
|
||||
if (e.getStatus().getCode() == ICModelStatusConstants.INVALID_PATH) {
|
||||
return null;
|
||||
}
|
||||
CCorePlugin.log(e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.eclipse.cdt.ui.CUIPlugin;
|
|||
import org.eclipse.cdt.internal.corext.util.CModelUtil;
|
||||
|
||||
import org.eclipse.cdt.internal.ui.viewsupport.AsyncTreeContentProvider;
|
||||
import org.eclipse.cdt.internal.ui.viewsupport.IndexUI;
|
||||
import org.eclipse.cdt.internal.ui.viewsupport.WorkingSetFilterUI;
|
||||
|
||||
/**
|
||||
|
@ -42,12 +43,14 @@ public class CHContentProvider extends AsyncTreeContentProvider {
|
|||
private static final IProgressMonitor NPM = new NullProgressMonitor();
|
||||
private boolean fComputeReferencedBy = true;
|
||||
private WorkingSetFilterUI fFilter;
|
||||
private CHViewPart fView;
|
||||
|
||||
/**
|
||||
* Constructs the content provider.
|
||||
*/
|
||||
public CHContentProvider(Display disp) {
|
||||
public CHContentProvider(CHViewPart view, Display disp) {
|
||||
super(disp);
|
||||
fView= view;
|
||||
}
|
||||
|
||||
public Object getParent(Object element) {
|
||||
|
@ -59,11 +62,6 @@ public class CHContentProvider extends AsyncTreeContentProvider {
|
|||
}
|
||||
|
||||
protected Object[] syncronouslyComputeChildren(Object parentElement) {
|
||||
if (parentElement instanceof ICElement) {
|
||||
ICElement element = (ICElement) parentElement;
|
||||
ITranslationUnit tu= CModelUtil.getTranslationUnit(element);
|
||||
return new Object[] { new CHNode(null, tu, 0, element) };
|
||||
}
|
||||
if (parentElement instanceof CHMultiDefNode) {
|
||||
return ((CHMultiDefNode) parentElement).getChildNodes();
|
||||
}
|
||||
|
@ -86,25 +84,58 @@ public class CHContentProvider extends AsyncTreeContentProvider {
|
|||
return null;
|
||||
}
|
||||
|
||||
protected Object[] asyncronouslyComputeChildren(Object parentElement,
|
||||
IProgressMonitor monitor) {
|
||||
protected Object[] asyncronouslyComputeChildren(Object parentElement, IProgressMonitor monitor) {
|
||||
try {
|
||||
if (parentElement instanceof ICElement) {
|
||||
return asyncComputeRoot((ICElement) parentElement);
|
||||
}
|
||||
|
||||
if (parentElement instanceof CHNode) {
|
||||
CHNode node = (CHNode) parentElement;
|
||||
try {
|
||||
if (fComputeReferencedBy) {
|
||||
return asyncronouslyComputeReferencedBy(node);
|
||||
}
|
||||
else {
|
||||
return asyncronouslyComputeRefersTo(node);
|
||||
}
|
||||
}
|
||||
} catch (CoreException e) {
|
||||
CUIPlugin.getDefault().log(e);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
return NO_CHILDREN;
|
||||
}
|
||||
|
||||
private Object[] asyncComputeRoot(final ICElement input) throws CoreException, InterruptedException {
|
||||
IIndex index= CCorePlugin.getIndexManager().getIndex(input.getCProject());
|
||||
index.acquireReadLock();
|
||||
try {
|
||||
ICElement element= input;
|
||||
if (!IndexUI.isIndexed(index, input)) {
|
||||
getDisplay().asyncExec(new Runnable() {
|
||||
public void run() {
|
||||
fView.reportNotIndexed(input);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
element= IndexUI.attemptConvertionToHandle(index, input);
|
||||
final ICElement finalElement= element;
|
||||
getDisplay().asyncExec(new Runnable() {
|
||||
public void run() {
|
||||
fView.reportInputReplacement(input, finalElement);
|
||||
}
|
||||
});
|
||||
}
|
||||
ITranslationUnit tu= CModelUtil.getTranslationUnit(element);
|
||||
return new Object[] { new CHNode(null, tu, 0, element) };
|
||||
}
|
||||
finally {
|
||||
index.releaseReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
private Object[] asyncronouslyComputeReferencedBy(CHNode parent) throws CoreException, InterruptedException {
|
||||
ICProject[] scope= CoreModel.getDefault().getCModel().getCProjects();
|
||||
IIndex index= CCorePlugin.getIndexManager().getIndex(scope);
|
||||
|
|
|
@ -77,6 +77,7 @@ import org.eclipse.cdt.internal.ui.viewsupport.AdaptingSelectionProvider;
|
|||
import org.eclipse.cdt.internal.ui.viewsupport.CElementLabels;
|
||||
import org.eclipse.cdt.internal.ui.viewsupport.EditorOpener;
|
||||
import org.eclipse.cdt.internal.ui.viewsupport.ExtendedTreeViewer;
|
||||
import org.eclipse.cdt.internal.ui.viewsupport.IndexUI;
|
||||
import org.eclipse.cdt.internal.ui.viewsupport.TreeNavigator;
|
||||
import org.eclipse.cdt.internal.ui.viewsupport.WorkingSetFilterUI;
|
||||
|
||||
|
@ -170,6 +171,19 @@ public class CHViewPart extends ViewPart {
|
|||
updateActionEnablement();
|
||||
}
|
||||
|
||||
public void reportNotIndexed(ICElement input) {
|
||||
if (input != null && getInput() == input) {
|
||||
setMessage(IndexUI.getFleNotIndexedMessage(input));
|
||||
}
|
||||
}
|
||||
|
||||
public void reportInputReplacement(ICElement input, ICElement inputHandle) {
|
||||
if (input == getInput()) {
|
||||
fTreeViewer.setInput(inputHandle);
|
||||
fTreeViewer.setExpandedState(inputHandle, true);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean allowsRefTo(ICElement element) {
|
||||
if (element instanceof IFunction || element instanceof IMethod) {
|
||||
return true;
|
||||
|
@ -290,7 +304,7 @@ public class CHViewPart extends ViewPart {
|
|||
fViewerPage.setSize(100, 100);
|
||||
fViewerPage.setLayout(new FillLayout());
|
||||
|
||||
fContentProvider= new CHContentProvider(display);
|
||||
fContentProvider= new CHContentProvider(this, display);
|
||||
fLabelProvider= new CHLabelProvider(display, fContentProvider);
|
||||
fTreeViewer= new ExtendedTreeViewer(fViewerPage);
|
||||
fTreeViewer.setContentProvider(fContentProvider);
|
||||
|
|
|
@ -166,8 +166,14 @@ public class CallHierarchyUI {
|
|||
else {
|
||||
ICElement[] elems= IndexUI.findAllDefinitions(index, binding);
|
||||
if (elems.length == 0) {
|
||||
ICElement elem= IndexUI.findAnyDeclaration(index, project, binding);
|
||||
if (elems != null) {
|
||||
ICElement elem= null;
|
||||
if (name.isDeclaration()) {
|
||||
elem= IndexUI.getCElementForName(project, index, name);
|
||||
}
|
||||
else {
|
||||
elem= IndexUI.findAnyDeclaration(index, project, binding);
|
||||
}
|
||||
if (elem != null) {
|
||||
elems= new ICElement[]{elem};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ public class IBMessages extends NLS {
|
|||
public static String IBViewPart_IncludedByContentDescription;
|
||||
public static String IBViewPart_IncludesToContentDescription;
|
||||
public static String IBViewPart_instructionMessage;
|
||||
|
||||
public static String IBViewPart_jobCheckInput;
|
||||
public static String IBViewPart_nextMatch_label;
|
||||
public static String IBViewPart_nextMatch_tooltip;
|
||||
public static String IBViewPart_OpenWithMenu_label;
|
||||
|
|
|
@ -31,6 +31,7 @@ IBViewPart_hideSystem_label=Hide System Includes
|
|||
IBViewPart_workspaceScope=workspace
|
||||
IBViewPart_refresh_label=Refresh
|
||||
IBViewPart_FocusOn_label=Focus On ''{0}''
|
||||
IBViewPart_jobCheckInput=Check Include Browser input
|
||||
IBViewPart_nextMatch_label=Next Include
|
||||
IBViewPart_refresh_tooltip=Refresh View Content
|
||||
IBViewPart_falseInputMessage=Include Hierarchies can be shown for C or C++ files, only. They have to be part of the workspace.
|
||||
|
|
|
@ -17,8 +17,13 @@ import java.util.Arrays;
|
|||
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Path;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.eclipse.core.runtime.jobs.Job;
|
||||
import org.eclipse.jface.action.Action;
|
||||
import org.eclipse.jface.action.IAction;
|
||||
import org.eclipse.jface.action.IMenuListener;
|
||||
|
@ -72,6 +77,7 @@ import org.eclipse.ui.part.ShowInContext;
|
|||
import org.eclipse.ui.part.ViewPart;
|
||||
|
||||
import org.eclipse.cdt.core.CCorePlugin;
|
||||
import org.eclipse.cdt.core.index.IIndex;
|
||||
import org.eclipse.cdt.core.index.IIndexFileLocation;
|
||||
import org.eclipse.cdt.core.index.IndexLocationFactory;
|
||||
import org.eclipse.cdt.core.model.CModelException;
|
||||
|
@ -87,6 +93,7 @@ import org.eclipse.cdt.internal.ui.navigator.OpenCElementAction;
|
|||
import org.eclipse.cdt.internal.ui.util.Messages;
|
||||
import org.eclipse.cdt.internal.ui.viewsupport.EditorOpener;
|
||||
import org.eclipse.cdt.internal.ui.viewsupport.ExtendedTreeViewer;
|
||||
import org.eclipse.cdt.internal.ui.viewsupport.IndexUI;
|
||||
import org.eclipse.cdt.internal.ui.viewsupport.TreeNavigator;
|
||||
import org.eclipse.cdt.internal.ui.viewsupport.WorkingSetFilterUI;
|
||||
|
||||
|
@ -176,7 +183,7 @@ public class IBViewPart extends ViewPart
|
|||
}
|
||||
}
|
||||
|
||||
private void setInputIndexerIdle(ITranslationUnit input) {
|
||||
private void setInputIndexerIdle(final ITranslationUnit input) {
|
||||
fShowsMessage= false;
|
||||
boolean isHeader= input.isHeaderUnit();
|
||||
|
||||
|
@ -197,6 +204,39 @@ public class IBViewPart extends ViewPart
|
|||
|
||||
updateActionEnablement();
|
||||
updateDescription();
|
||||
final Display display= Display.getCurrent();
|
||||
Job job= new Job(IBMessages.IBViewPart_jobCheckInput) {
|
||||
protected IStatus run(IProgressMonitor monitor) {
|
||||
try {
|
||||
IIndex index= CCorePlugin.getIndexManager().getIndex(input.getCProject());
|
||||
index.acquireReadLock();
|
||||
try {
|
||||
if (!IndexUI.isIndexed(index, input)) {
|
||||
final String msg = IndexUI
|
||||
.getFleNotIndexedMessage(input);
|
||||
display.asyncExec(new Runnable() {
|
||||
public void run() {
|
||||
if (fTreeViewer.getInput() == input) {
|
||||
setMessage(msg);
|
||||
fTreeViewer.setInput(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return Status.OK_STATUS;
|
||||
} finally {
|
||||
index.releaseReadLock();
|
||||
}
|
||||
} catch (CoreException e) {
|
||||
return Status.OK_STATUS;
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return Status.CANCEL_STATUS;
|
||||
}
|
||||
}
|
||||
};
|
||||
job.setSystem(true);
|
||||
job.schedule();
|
||||
}
|
||||
|
||||
private void updateActionEnablement() {
|
||||
|
|
|
@ -46,6 +46,7 @@ class THGraph {
|
|||
private HashSet fRootNodes= new HashSet();
|
||||
private HashSet fLeaveNodes= new HashSet();
|
||||
private HashMap fNodes= new HashMap();
|
||||
private boolean fFileIsIndexed;
|
||||
|
||||
public THGraph() {
|
||||
}
|
||||
|
@ -128,17 +129,13 @@ class THGraph {
|
|||
}
|
||||
|
||||
public void defineInputNode(IIndex index, ICElement input) {
|
||||
if (input instanceof ICElementHandle) {
|
||||
if (input != null) {
|
||||
try {
|
||||
if (IndexUI.isIndexed(index, input)) {
|
||||
fFileIsIndexed= true;
|
||||
input= IndexUI.attemptConvertionToHandle(index, input);
|
||||
fInputNode= addNode(input);
|
||||
}
|
||||
else if (input != null) {
|
||||
try {
|
||||
ICElement inputHandle= null;
|
||||
IIndexName name= IndexUI.elementToName(index, input);
|
||||
if (name != null) {
|
||||
inputHandle= IndexUI.getCElementForName(input.getCProject(), index, name);
|
||||
}
|
||||
fInputNode= addNode(inputHandle == null ? input : inputHandle);
|
||||
} catch (CoreException e) {
|
||||
CUIPlugin.getDefault().log(e);
|
||||
}
|
||||
|
@ -307,4 +304,8 @@ class THGraph {
|
|||
public boolean isTrivial() {
|
||||
return fNodes.size() < 2;
|
||||
}
|
||||
|
||||
public boolean isFileIndexed() {
|
||||
return fFileIsIndexed;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.eclipse.cdt.core.model.ICElement;
|
|||
import org.eclipse.cdt.core.model.ICProject;
|
||||
import org.eclipse.cdt.ui.CUIPlugin;
|
||||
|
||||
import org.eclipse.cdt.internal.ui.viewsupport.IndexUI;
|
||||
import org.eclipse.cdt.internal.ui.viewsupport.WorkingSetFilterUI;
|
||||
|
||||
class THHierarchyModel {
|
||||
|
@ -317,7 +318,10 @@ class THHierarchyModel {
|
|||
public void run() {
|
||||
fGraph= graph;
|
||||
THGraphNode inputNode= fGraph.getInputNode();
|
||||
if (inputNode == null) {
|
||||
if (!fGraph.isFileIndexed()) {
|
||||
fView.setMessage(IndexUI.getFleNotIndexedMessage(fInput));
|
||||
}
|
||||
else if (inputNode == null) {
|
||||
fView.setMessage(Messages.THHierarchyModel_errorComputingHierarchy);
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2006 Wind River Systems, Inc. and others.
|
||||
* Copyright (c) 2006, 2007 Wind River Systems, Inc. and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
|
@ -11,15 +11,25 @@
|
|||
|
||||
package org.eclipse.cdt.internal.ui.viewsupport;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.eclipse.cdt.internal.ui.CUIMessages;
|
||||
import org.eclipse.core.runtime.*;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.eclipse.core.runtime.jobs.Job;
|
||||
import org.eclipse.jface.viewers.*;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.jface.viewers.ITreeContentProvider;
|
||||
import org.eclipse.jface.viewers.StructuredSelection;
|
||||
import org.eclipse.jface.viewers.TreeViewer;
|
||||
import org.eclipse.jface.viewers.Viewer;
|
||||
import org.eclipse.swt.SWTException;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
|
||||
import org.eclipse.cdt.internal.ui.CUIMessages;
|
||||
|
||||
/**
|
||||
* A TreeContentProvider that supports asyncronous computation of child nodes.
|
||||
* <p>
|
||||
|
@ -339,4 +349,8 @@ public abstract class AsyncTreeContentProvider implements ITreeContentProvider {
|
|||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
final protected Display getDisplay() {
|
||||
return fDisplay;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,8 +13,10 @@
|
|||
package org.eclipse.cdt.internal.ui.viewsupport;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
|
@ -24,6 +26,7 @@ import org.eclipse.core.runtime.Status;
|
|||
import org.eclipse.jface.text.IRegion;
|
||||
import org.eclipse.jface.text.ITextSelection;
|
||||
import org.eclipse.jface.text.Region;
|
||||
import org.eclipse.osgi.util.NLS;
|
||||
import org.eclipse.ui.IEditorInput;
|
||||
|
||||
import org.eclipse.cdt.core.CCorePlugin;
|
||||
|
@ -70,6 +73,7 @@ import org.eclipse.cdt.ui.CUIPlugin;
|
|||
import org.eclipse.cdt.internal.core.model.ASTCache.ASTRunnable;
|
||||
import org.eclipse.cdt.internal.core.model.ext.CElementHandleFactory;
|
||||
import org.eclipse.cdt.internal.core.model.ext.ICElementHandle;
|
||||
import org.eclipse.cdt.internal.core.pdom.indexer.IndexerPreferences;
|
||||
|
||||
import org.eclipse.cdt.internal.ui.editor.ASTProvider;
|
||||
|
||||
|
@ -184,6 +188,21 @@ public class IndexUI {
|
|||
return null;
|
||||
}
|
||||
|
||||
public static boolean isIndexed(IIndex index, ICElement element) throws CoreException {
|
||||
if (element instanceof ISourceReference) {
|
||||
ISourceReference sf = ((ISourceReference)element);
|
||||
ITranslationUnit tu= sf.getTranslationUnit();
|
||||
if (tu != null) {
|
||||
IIndexFileLocation location= IndexLocationFactory.getIFL(tu);
|
||||
if (location != null) {
|
||||
IIndexFile file= index.getFile(location);
|
||||
return file != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IRegion getConvertedRegion(ITranslationUnit tu, IIndexFile file, int pos, int length) throws CoreException {
|
||||
IRegion region= new Region(pos, length);
|
||||
IPositionConverter converter= CCorePlugin.getPositionTrackerManager().findPositionConverter(tu, file.getTimestamp());
|
||||
|
@ -349,4 +368,40 @@ public class IndexUI {
|
|||
});
|
||||
return result[0];
|
||||
}
|
||||
|
||||
public static String getFleNotIndexedMessage(ICElement input) {
|
||||
ITranslationUnit tu= null;
|
||||
if (input instanceof ISourceReference) {
|
||||
ISourceReference ref= (ISourceReference) input;
|
||||
tu= ref.getTranslationUnit();
|
||||
}
|
||||
if (tu == null) {
|
||||
return NLS.bind(Messages.IndexUI_infoNotInSource, input.getElementName());
|
||||
}
|
||||
|
||||
String msg= NLS.bind(Messages.IndexUI_infoNotInIndex, tu.getElementName());
|
||||
|
||||
IResource res= tu.getResource();
|
||||
if (res != null) {
|
||||
Properties props= IndexerPreferences.getProperties(res.getProject());
|
||||
if (props == null || !"true".equals(props.get(IndexerPreferences.KEY_INDEX_ALL_FILES))) { //$NON-NLS-1$
|
||||
msg= msg+ " " + Messages.IndexUI_infoSelectIndexAllFiles; //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static ICElement attemptConvertionToHandle(IIndex index, ICElement input) throws CoreException {
|
||||
if (input instanceof ICElementHandle) {
|
||||
return input;
|
||||
}
|
||||
IIndexName name= IndexUI.elementToName(index, input);
|
||||
if (name != null) {
|
||||
ICElement handle= getCElementForName(input.getCProject(), index, name);
|
||||
if (handle != null) {
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2007 Wind River Systems, Inc. and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Markus Schorn - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.eclipse.cdt.internal.ui.viewsupport;
|
||||
|
||||
import org.eclipse.osgi.util.NLS;
|
||||
|
||||
public class Messages extends NLS {
|
||||
private static final String BUNDLE_NAME = "org.eclipse.cdt.internal.ui.viewsupport.messages"; //$NON-NLS-1$
|
||||
public static String IndexUI_infoNotInIndex;
|
||||
public static String IndexUI_infoNotInSource;
|
||||
public static String IndexUI_infoSelectIndexAllFiles;
|
||||
static {
|
||||
// initialize resource bundle
|
||||
NLS.initializeMessages(BUNDLE_NAME, Messages.class);
|
||||
}
|
||||
|
||||
private Messages() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
################################################################################
|
||||
# Copyright (c) 2007 Wind River Systems, Inc. and others.
|
||||
# All rights reserved. This program and the accompanying materials
|
||||
# are made available under the terms of the Eclipse Public License v1.0
|
||||
# which accompanies this distribution, and is available at
|
||||
# http://www.eclipse.org/legal/epl-v10.html
|
||||
#
|
||||
# Contributors:
|
||||
# Markus Schorn - initial API and implementation
|
||||
################################################################################
|
||||
IndexUI_infoNotInSource=The element ''{0}'' does not belong to a source file.
|
||||
IndexUI_infoNotInIndex=The file ''{0}'' is currently not part of the index.
|
||||
IndexUI_infoSelectIndexAllFiles=For headers that are never included, or sources that are not part of the build, consider selecting the preference 'Index all files'.
|
Loading…
Add table
Reference in a new issue