mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-CR-50999: Added ability to pass ImplementationViewElement in asynchronous implementation view search.
GitOrigin-RevId: 3796f503b014f8268f9e992508df23c8cf17856b
This commit is contained in:
committed by
intellij-monorepo-bot
parent
a7bc072e78
commit
f136cc3284
@@ -13,6 +13,9 @@ import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiNamedElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.usageView.UsageInfo
|
||||
import com.intellij.usages.Usage
|
||||
import com.intellij.usages.UsageInfo2UsageAdapter
|
||||
import javax.swing.Icon
|
||||
|
||||
/**
|
||||
@@ -33,6 +36,11 @@ abstract class ImplementationViewElement {
|
||||
abstract val elementForShowUsages: PsiElement?
|
||||
|
||||
abstract fun navigate(focusEditor: Boolean)
|
||||
|
||||
open val usage: Usage?
|
||||
get() {
|
||||
return UsageInfo2UsageAdapter(UsageInfo(elementForShowUsages ?: return null))
|
||||
}
|
||||
}
|
||||
|
||||
class PsiImplementationViewElement(val psiElement: PsiElement) : ImplementationViewElement() {
|
||||
|
||||
@@ -13,6 +13,9 @@ import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.presentation.java.SymbolPresentationUtil
|
||||
import com.intellij.usageView.UsageInfo
|
||||
import com.intellij.usages.Usage
|
||||
import com.intellij.usages.UsageInfo2UsageAdapter
|
||||
import com.intellij.util.Processor
|
||||
|
||||
interface ImplementationViewSession : Disposable {
|
||||
@@ -30,7 +33,7 @@ interface ImplementationViewSession : Disposable {
|
||||
val editor: Editor?
|
||||
|
||||
fun searchImplementationsInBackground(indicator: ProgressIndicator,
|
||||
processor: Processor<in PsiElement>): List<ImplementationViewElement>
|
||||
processor: Processor<in ImplementationViewElement>): List<ImplementationViewElement>
|
||||
fun elementRequiresIncludeSelf(): Boolean
|
||||
fun needUpdateInBackground(): Boolean
|
||||
}
|
||||
|
||||
+2
-2
@@ -195,7 +195,7 @@ public class PsiImplementationViewSession implements ImplementationViewSession {
|
||||
@NotNull
|
||||
@Override
|
||||
public List<ImplementationViewElement> searchImplementationsInBackground(@NotNull ProgressIndicator indicator,
|
||||
@NotNull final Processor<? super PsiElement> processor) {
|
||||
@NotNull final Processor<? super ImplementationViewElement> processor) {
|
||||
final ImplementationSearcher.BackgroundableImplementationSearcher implementationSearcher =
|
||||
new ImplementationSearcher.BackgroundableImplementationSearcher() {
|
||||
@Override
|
||||
@@ -205,7 +205,7 @@ public class PsiImplementationViewSession implements ImplementationViewSession {
|
||||
|
||||
@Override
|
||||
protected void processElement(PsiElement element) {
|
||||
if (!processor.process(element)) {
|
||||
if (!processor.process(new PsiImplementationViewElement(element))) {
|
||||
indicator.cancel();
|
||||
}
|
||||
indicator.checkCanceled();
|
||||
|
||||
+13
-8
@@ -5,7 +5,7 @@ import com.intellij.codeInsight.CodeInsightBundle;
|
||||
import com.intellij.codeInsight.documentation.DocumentationManager;
|
||||
import com.intellij.codeInsight.hint.*;
|
||||
import com.intellij.codeInsight.lookup.LookupManager;
|
||||
import com.intellij.codeInsight.navigation.BackgroundUpdaterTask;
|
||||
import com.intellij.codeInsight.navigation.BackgroundUpdaterTaskBase;
|
||||
import com.intellij.codeInsight.navigation.ImplementationSearcher;
|
||||
import com.intellij.featureStatistics.FeatureUsageTracker;
|
||||
import com.intellij.ide.DataManager;
|
||||
@@ -20,18 +20,18 @@ import com.intellij.openapi.project.DumbAwareAction;
|
||||
import com.intellij.openapi.project.DumbService;
|
||||
import com.intellij.openapi.project.IndexNotReadyException;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.ListComponentUpdater;
|
||||
import com.intellij.openapi.ui.GenericListComponentUpdater;
|
||||
import com.intellij.openapi.ui.popup.JBPopup;
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.reference.SoftReference;
|
||||
import com.intellij.ui.popup.AbstractPopup;
|
||||
import com.intellij.ui.popup.PopupPositionManager;
|
||||
import com.intellij.ui.popup.PopupUpdateProcessor;
|
||||
import com.intellij.usages.Usage;
|
||||
import com.intellij.usages.UsageView;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -229,7 +229,7 @@ public class ShowImplementationsAction extends DumbAwareAction implements PopupA
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class ImplementationViewComponentUpdater implements ListComponentUpdater {
|
||||
private static class ImplementationViewComponentUpdater implements GenericListComponentUpdater<ImplementationViewElement> {
|
||||
private final ImplementationViewComponent myComponent;
|
||||
private final int myIncludeSelfIdx;
|
||||
|
||||
@@ -244,20 +244,20 @@ public class ShowImplementationsAction extends DumbAwareAction implements PopupA
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceModel(@NotNull List<? extends PsiElement> data) {
|
||||
public void replaceModel(@NotNull List<? extends ImplementationViewElement> data) {
|
||||
final ImplementationViewElement[] elements = myComponent.getElements();
|
||||
final int includeSelfIdx = myIncludeSelfIdx;
|
||||
final int startIdx = elements.length - includeSelfIdx;
|
||||
List<ImplementationViewElement> result = new ArrayList<>();
|
||||
Collections.addAll(result, elements);
|
||||
for (PsiElement element : data.subList(startIdx, data.size())) {
|
||||
result.add(new PsiImplementationViewElement(element));
|
||||
for (ImplementationViewElement element : data.subList(startIdx, data.size())) {
|
||||
result.add(element);
|
||||
}
|
||||
myComponent.update(result, myComponent.getIndex());
|
||||
}
|
||||
}
|
||||
|
||||
private static class ImplementationsUpdaterTask extends BackgroundUpdaterTask {
|
||||
private static class ImplementationsUpdaterTask extends BackgroundUpdaterTaskBase<ImplementationViewElement> {
|
||||
private final String myCaption;
|
||||
private final ImplementationViewSession mySession;
|
||||
private final ImplementationViewComponent myComponent;
|
||||
@@ -277,6 +277,11 @@ public class ShowImplementationsAction extends DumbAwareAction implements PopupA
|
||||
return myCaption;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Usage createUsage(ImplementationViewElement element) {
|
||||
return element.getUsage();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run(@NotNull final ProgressIndicator indicator) {
|
||||
|
||||
+6
-166
@@ -15,66 +15,22 @@
|
||||
*/
|
||||
package com.intellij.codeInsight.navigation;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.ListComponentUpdater;
|
||||
import com.intellij.openapi.ui.popup.JBPopup;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.intellij.usageView.UsageInfo;
|
||||
import com.intellij.usages.Usage;
|
||||
import com.intellij.usages.UsageInfo2UsageAdapter;
|
||||
import com.intellij.usages.UsageView;
|
||||
import com.intellij.usages.impl.UsageViewImpl;
|
||||
import com.intellij.util.Alarm;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public abstract class BackgroundUpdaterTask extends Task.Backgroundable {
|
||||
protected JBPopup myPopup;
|
||||
private ListComponentUpdater myUpdater;
|
||||
private Ref<? extends UsageView> myUsageView;
|
||||
private final Collection<PsiElement> myData;
|
||||
|
||||
private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
|
||||
private final Object lock = new Object();
|
||||
|
||||
private volatile boolean myCanceled;
|
||||
private volatile boolean myFinished;
|
||||
private volatile ProgressIndicator myIndicator;
|
||||
public abstract class BackgroundUpdaterTask extends BackgroundUpdaterTaskBase<PsiElement> {
|
||||
|
||||
public BackgroundUpdaterTask(@Nullable Project project, @NotNull String title, @Nullable Comparator<PsiElement> comparator) {
|
||||
super(project, title);
|
||||
myData = comparator == null ? ContainerUtil.newSmartList() : new TreeSet<>(comparator);
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public ListComponentUpdater getUpdater() {
|
||||
return myUpdater;
|
||||
}
|
||||
|
||||
public void init(@NotNull JBPopup popup, @NotNull ListComponentUpdater updater, @NotNull Ref<? extends UsageView> usageView) {
|
||||
myPopup = popup;
|
||||
myUpdater = updater;
|
||||
myUsageView = usageView;
|
||||
}
|
||||
|
||||
public abstract String getCaption(int size);
|
||||
|
||||
protected void replaceModel(@NotNull List<? extends PsiElement> data) {
|
||||
myUpdater.replaceModel(data);
|
||||
}
|
||||
|
||||
protected void paintBusy(boolean paintBusy) {
|
||||
myUpdater.paintBusy(paintBusy);
|
||||
super(project, title, comparator);
|
||||
}
|
||||
|
||||
protected static Comparator<PsiElement> createComparatorWrapper(@NotNull Comparator<? super PsiElement> comparator) {
|
||||
@@ -87,125 +43,9 @@ public abstract class BackgroundUpdaterTask extends Task.Backgroundable {
|
||||
};
|
||||
}
|
||||
|
||||
private boolean setCanceled() {
|
||||
boolean canceled = myCanceled;
|
||||
myCanceled = true;
|
||||
return canceled;
|
||||
}
|
||||
|
||||
public boolean isCanceled() {
|
||||
return myCanceled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #BackgroundUpdaterTask(Project, String, Comparator)} and {@link #updateComponent(PsiElement)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean updateComponent(@NotNull PsiElement element, @Nullable Comparator comparator) {
|
||||
final UsageView view = myUsageView.get();
|
||||
if (view != null && !((UsageViewImpl)view).isDisposed()) {
|
||||
ApplicationManager.getApplication().runReadAction(() -> view.appendUsage(new UsageInfo2UsageAdapter(new UsageInfo(element))));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (myCanceled) return false;
|
||||
|
||||
if (myPopup.isDisposed()) return false;
|
||||
ModalityState modalityState = ModalityState.stateForComponent(myPopup.getContent());
|
||||
|
||||
synchronized (lock) {
|
||||
if (myData.contains(element)) return true;
|
||||
myData.add(element);
|
||||
if (comparator != null && myData instanceof List) {
|
||||
Collections.sort((List)myData, comparator);
|
||||
}
|
||||
}
|
||||
|
||||
myAlarm.addRequest(() -> {
|
||||
myAlarm.cancelAllRequests();
|
||||
refreshModelImmediately();
|
||||
}, 200, modalityState);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean updateComponent(@NotNull PsiElement element) {
|
||||
final UsageView view = myUsageView.get();
|
||||
if (view != null && !((UsageViewImpl)view).isDisposed()) {
|
||||
ApplicationManager.getApplication().runReadAction(() -> view.appendUsage(new UsageInfo2UsageAdapter(new UsageInfo(element))));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (myCanceled) return false;
|
||||
if (myPopup.isDisposed()) return false;
|
||||
|
||||
synchronized (lock) {
|
||||
if (!myData.add(element)) return true;
|
||||
}
|
||||
|
||||
myAlarm.addRequest(() -> {
|
||||
myAlarm.cancelAllRequests();
|
||||
refreshModelImmediately();
|
||||
}, 200, ModalityState.stateForComponent(myPopup.getContent()));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void refreshModelImmediately() {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||
if (myCanceled) return;
|
||||
if (myPopup.isDisposed()) return;
|
||||
List<PsiElement> data;
|
||||
synchronized (lock) {
|
||||
data = new ArrayList<>(myData);
|
||||
}
|
||||
replaceModel(data);
|
||||
myPopup.setCaption(getCaption(getCurrentSize()));
|
||||
myPopup.pack(true, true);
|
||||
}
|
||||
|
||||
public int getCurrentSize() {
|
||||
synchronized (lock) {
|
||||
return myData.size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(@NotNull ProgressIndicator indicator) {
|
||||
paintBusy(true);
|
||||
myIndicator = indicator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
myFinished = true;
|
||||
refreshModelImmediately();
|
||||
paintBusy(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinished() {
|
||||
myAlarm.cancelAllRequests();
|
||||
myFinished = true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected PsiElement getTheOnlyOneElement() {
|
||||
synchronized (lock) {
|
||||
if (myData.size() == 1) {
|
||||
return myData.iterator().next();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isFinished() {
|
||||
return myFinished;
|
||||
}
|
||||
|
||||
public boolean cancelTask() {
|
||||
ProgressIndicator indicator = myIndicator;
|
||||
if (indicator != null) {
|
||||
indicator.cancel();
|
||||
}
|
||||
return setCanceled();
|
||||
protected Usage createUsage(PsiElement element) {
|
||||
return new UsageInfo2UsageAdapter(new UsageInfo(element));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.codeInsight.navigation;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.GenericListComponentUpdater;
|
||||
import com.intellij.openapi.ui.popup.JBPopup;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.usages.Usage;
|
||||
import com.intellij.usages.UsageView;
|
||||
import com.intellij.usages.impl.UsageViewImpl;
|
||||
import com.intellij.util.Alarm;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public abstract class BackgroundUpdaterTaskBase<T> extends Task.Backgroundable {
|
||||
protected JBPopup myPopup;
|
||||
private GenericListComponentUpdater<T> myUpdater;
|
||||
private Ref<? extends UsageView> myUsageView;
|
||||
private final Collection<T> myData;
|
||||
|
||||
private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
|
||||
private final Object lock = new Object();
|
||||
|
||||
private volatile boolean myCanceled;
|
||||
private volatile boolean myFinished;
|
||||
private volatile ProgressIndicator myIndicator;
|
||||
|
||||
public BackgroundUpdaterTaskBase(@Nullable Project project, @NotNull String title, @Nullable Comparator<T> comparator) {
|
||||
super(project, title);
|
||||
myData = comparator == null ? ContainerUtil.newSmartList() : new TreeSet<>(comparator);
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public GenericListComponentUpdater<T> getUpdater() {
|
||||
return myUpdater;
|
||||
}
|
||||
|
||||
public void init(@NotNull JBPopup popup, @NotNull GenericListComponentUpdater<T> updater, @NotNull Ref<? extends UsageView> usageView) {
|
||||
myPopup = popup;
|
||||
myUpdater = updater;
|
||||
myUsageView = usageView;
|
||||
}
|
||||
|
||||
public abstract String getCaption(int size);
|
||||
|
||||
@Nullable
|
||||
protected abstract Usage createUsage(T element);
|
||||
|
||||
protected void replaceModel(@NotNull List<? extends T> data) {
|
||||
myUpdater.replaceModel(data);
|
||||
}
|
||||
|
||||
protected void paintBusy(boolean paintBusy) {
|
||||
myUpdater.paintBusy(paintBusy);
|
||||
}
|
||||
|
||||
private boolean setCanceled() {
|
||||
boolean canceled = myCanceled;
|
||||
myCanceled = true;
|
||||
return canceled;
|
||||
}
|
||||
|
||||
public boolean isCanceled() {
|
||||
return myCanceled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #BackgroundUpdaterTaskBase(Project, String, Comparator)} and {@link #updateComponent(T)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean updateComponent(@NotNull T element, @Nullable Comparator comparator) {
|
||||
if (tryAppendUsage(element)) return true;
|
||||
if (myCanceled) return false;
|
||||
|
||||
if (myPopup.isDisposed()) return false;
|
||||
ModalityState modalityState = ModalityState.stateForComponent(myPopup.getContent());
|
||||
|
||||
synchronized (lock) {
|
||||
if (myData.contains(element)) return true;
|
||||
myData.add(element);
|
||||
if (comparator != null && myData instanceof List) {
|
||||
Collections.sort((List)myData, comparator);
|
||||
}
|
||||
}
|
||||
|
||||
myAlarm.addRequest(() -> {
|
||||
myAlarm.cancelAllRequests();
|
||||
refreshModelImmediately();
|
||||
}, 200, modalityState);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean tryAppendUsage(@NotNull T element) {
|
||||
final UsageView view = myUsageView.get();
|
||||
if (view != null && !((UsageViewImpl)view).isDisposed()) {
|
||||
Usage usage = createUsage(element);
|
||||
if (usage == null)
|
||||
return false;
|
||||
ApplicationManager.getApplication().runReadAction(() -> view.appendUsage(usage));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean updateComponent(@NotNull T element) {
|
||||
if (tryAppendUsage(element)) return true;
|
||||
|
||||
if (myCanceled) return false;
|
||||
if (myPopup.isDisposed()) return false;
|
||||
|
||||
synchronized (lock) {
|
||||
if (!myData.add(element)) return true;
|
||||
}
|
||||
|
||||
myAlarm.addRequest(() -> {
|
||||
myAlarm.cancelAllRequests();
|
||||
refreshModelImmediately();
|
||||
}, 200, ModalityState.stateForComponent(myPopup.getContent()));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void refreshModelImmediately() {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||
if (myCanceled) return;
|
||||
if (myPopup.isDisposed()) return;
|
||||
List<T> data;
|
||||
synchronized (lock) {
|
||||
data = new ArrayList<>(myData);
|
||||
}
|
||||
replaceModel(data);
|
||||
myPopup.setCaption(getCaption(getCurrentSize()));
|
||||
myPopup.pack(true, true);
|
||||
}
|
||||
|
||||
public int getCurrentSize() {
|
||||
synchronized (lock) {
|
||||
return myData.size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(@NotNull ProgressIndicator indicator) {
|
||||
paintBusy(true);
|
||||
myIndicator = indicator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
myFinished = true;
|
||||
refreshModelImmediately();
|
||||
paintBusy(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinished() {
|
||||
myAlarm.cancelAllRequests();
|
||||
myFinished = true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected T getTheOnlyOneElement() {
|
||||
synchronized (lock) {
|
||||
if (myData.size() == 1) {
|
||||
return myData.iterator().next();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isFinished() {
|
||||
return myFinished;
|
||||
}
|
||||
|
||||
public boolean cancelTask() {
|
||||
ProgressIndicator indicator = myIndicator;
|
||||
if (indicator != null) {
|
||||
indicator.cancel();
|
||||
}
|
||||
return setCanceled();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.openapi.ui;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface GenericListComponentUpdater<T> {
|
||||
void replaceModel(@NotNull List<? extends T> data);
|
||||
void paintBusy(boolean paintBusy);
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ListComponentUpdater {
|
||||
void replaceModel(@NotNull List<? extends PsiElement> data);
|
||||
void paintBusy(boolean paintBusy);
|
||||
public interface ListComponentUpdater extends GenericListComponentUpdater<PsiElement> {
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user