mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Merge branch 'master' of git.labs.intellij.net:idea/community
This commit is contained in:
@@ -8,5 +8,6 @@
|
||||
<lang.syntaxHighlighterFactory key="RegExp" implementationClass="org.intellij.lang.regexp.RegExpSyntaxHighlighterFactory"/>
|
||||
<lang.braceMatcher language="RegExp" implementationClass="org.intellij.lang.regexp.RegExpBraceMatcher"/>
|
||||
<lang.surroundDescriptor language="RegExp" implementationClass="org.intellij.lang.regexp.surroundWith.SimpleSurroundDescriptor"/>
|
||||
<colorSettingsPage implementation="org.intellij.lang.regexp.RegExpColorsPage"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2000-2010 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed 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.intellij.lang.regexp;
|
||||
|
||||
import com.intellij.application.options.colors.InspectionColorSettingsPage;
|
||||
import com.intellij.openapi.editor.colors.TextAttributesKey;
|
||||
import com.intellij.openapi.fileTypes.SyntaxHighlighter;
|
||||
import com.intellij.openapi.options.colors.AttributesDescriptor;
|
||||
import com.intellij.openapi.options.colors.ColorDescriptor;
|
||||
import com.intellij.openapi.options.colors.ColorSettingsPage;
|
||||
import com.intellij.util.containers.HashMap;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author traff
|
||||
*/
|
||||
public class RegExpColorsPage implements ColorSettingsPage, InspectionColorSettingsPage {
|
||||
private static final AttributesDescriptor[] ATTRS = new AttributesDescriptor[] {
|
||||
new AttributesDescriptor("Keywords", RegExpHighlighter.META),
|
||||
new AttributesDescriptor("Escaped characters", RegExpHighlighter.ESC_CHARACTER),
|
||||
new AttributesDescriptor("Braces", RegExpHighlighter.BRACES),
|
||||
new AttributesDescriptor("Brackets", RegExpHighlighter.BRACKETS),
|
||||
new AttributesDescriptor("Parenthesis", RegExpHighlighter.PARENTHS),
|
||||
};
|
||||
|
||||
@NonNls private static final HashMap<String,TextAttributesKey> ourTagToDescriptorMap = new HashMap<String, TextAttributesKey>();
|
||||
|
||||
@NotNull
|
||||
public String getDisplayName() {
|
||||
return "RegExp";
|
||||
}
|
||||
|
||||
public Icon getIcon() {
|
||||
return RegExpFileType.INSTANCE.getIcon();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public AttributesDescriptor[] getAttributeDescriptors() {
|
||||
return ATTRS;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ColorDescriptor[] getColorDescriptors() {
|
||||
return ColorDescriptor.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public SyntaxHighlighter getHighlighter() {
|
||||
final SyntaxHighlighter highlighter = SyntaxHighlighter.PROVIDER.create(RegExpFileType.INSTANCE, null, null);
|
||||
assert highlighter != null;
|
||||
return highlighter;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getDemoText() {
|
||||
return
|
||||
"^[\\w\\.-]+@([\\w\\-]+\\.)+[A-Z]{2,4}$";
|
||||
|
||||
}
|
||||
|
||||
public Map<String, TextAttributesKey> getAdditionalHighlightingTagToDescriptorMap() {
|
||||
return ourTagToDescriptorMap;
|
||||
}
|
||||
}
|
||||
+36
-15
@@ -14,12 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Created by IntelliJ IDEA.
|
||||
* User: Anna.Kozlova
|
||||
* Date: 05-Nov-2006
|
||||
* Time: 18:04:58
|
||||
*/
|
||||
package com.intellij.codeInsight.intention;
|
||||
|
||||
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
|
||||
@@ -28,9 +22,45 @@ import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author Anna Kozlova
|
||||
* @author Konstantin Bulenkov
|
||||
*/
|
||||
public abstract class PsiElementBaseIntentionAction extends BaseIntentionAction {
|
||||
/**
|
||||
* Invokes intention action for the element under cursor
|
||||
*
|
||||
* @param project the project in which the file is opened.
|
||||
* @param editor the editor for the file
|
||||
* @param element the element under cursor
|
||||
|
||||
* @throws com.intellij.util.IncorrectOperationException ...
|
||||
*/
|
||||
public void invoke(Project project, Editor editor, PsiElement element) throws IncorrectOperationException {
|
||||
throw new IncorrectOperationException();
|
||||
}
|
||||
|
||||
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
|
||||
final PsiElement element = getElement(editor, file);
|
||||
return element == null ? false : isAvailable(project, editor, element);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected static PsiElement getElement(Editor editor, PsiFile file) {
|
||||
if (!file.getManager().isInProject(file)) return null;
|
||||
final CaretModel caretModel = editor.getCaretModel();
|
||||
final int position = caretModel.getOffset();
|
||||
return file.findElementAt(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
|
||||
invoke(project, editor, getElement(editor, file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this intention is available at a caret offset in file.
|
||||
@@ -42,13 +72,4 @@ public abstract class PsiElementBaseIntentionAction extends BaseIntentionAction
|
||||
* @return true if the intention is available, false otherwise.
|
||||
*/
|
||||
public abstract boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element);
|
||||
|
||||
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
|
||||
if (!file.getManager().isInProject(file)) return false;
|
||||
final CaretModel caretModel = editor.getCaretModel();
|
||||
final int position = caretModel.getOffset();
|
||||
final PsiElement element = file.findElementAt(position);
|
||||
if (element == null) return false;
|
||||
return isAvailable(project, editor, element);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -2,4 +2,4 @@
|
||||
<body>
|
||||
<font face="verdana" size="-1">This inspection detects missing html-description for an inspection.</font>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -17,18 +17,22 @@ package org.jetbrains.idea.devkit;
|
||||
|
||||
import com.intellij.codeInspection.InspectionToolProvider;
|
||||
import org.jetbrains.idea.devkit.inspections.ComponentNotRegisteredInspection;
|
||||
import org.jetbrains.idea.devkit.inspections.DescriptionNotFoundInspection;
|
||||
import org.jetbrains.idea.devkit.inspections.InspectionDescriptionNotFoundInspection;
|
||||
import org.jetbrains.idea.devkit.inspections.IntentionDescriptionNotFoundInspection;
|
||||
import org.jetbrains.idea.devkit.inspections.PluginXmlDomInspection;
|
||||
|
||||
/**
|
||||
* @author Konstantin Bulenkov
|
||||
*/
|
||||
public class DevKitInspectionToolProvider implements InspectionToolProvider {
|
||||
|
||||
public Class[] getInspectionClasses() {
|
||||
return new Class[] {
|
||||
//RegistrationProblemsInspection.class,
|
||||
PluginXmlDomInspection.class,
|
||||
ComponentNotRegisteredInspection.class,
|
||||
DescriptionNotFoundInspection.class
|
||||
InspectionDescriptionNotFoundInspection.class,
|
||||
IntentionDescriptionNotFoundInspection.class
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -41,7 +41,7 @@ import java.util.List;
|
||||
/**
|
||||
* @author Konstantin Bulenkov
|
||||
*/
|
||||
public class DescriptionNotFoundInspection extends DevKitInspectionBase{
|
||||
public class InspectionDescriptionNotFoundInspection extends DevKitInspectionBase{
|
||||
@NonNls private static final String INSPECTION_PROFILE_ENTRY = "com.intellij.codeInspection.InspectionProfileEntry";
|
||||
@NonNls private static final String INSPECTION_DESCRIPTIONS = "inspectionDescriptions";
|
||||
|
||||
@@ -77,7 +77,7 @@ public class DescriptionNotFoundInspection extends DevKitInspectionBase{
|
||||
final PsiElement problem = getProblemElement(aClass, method);
|
||||
final ProblemDescriptor problemDescriptor = manager
|
||||
.createProblemDescriptor(problem == null ? nameIdentifier : problem,
|
||||
"Inspection does not have a description", isOnTheFly, new LocalQuickFix[]{new CreateHtmlDescriptionFix(filename, module)},
|
||||
"Inspection does not have a description", isOnTheFly, new LocalQuickFix[]{new CreateHtmlDescriptionFix(filename, module, false)},
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
|
||||
return new ProblemDescriptor[]{problemDescriptor};
|
||||
}
|
||||
@@ -152,7 +152,7 @@ public class DescriptionNotFoundInspection extends DevKitInspectionBase{
|
||||
|
||||
@NotNull
|
||||
public String getShortName() {
|
||||
return "DescriptionNotFoundInspection";
|
||||
return "InspectionDescriptionNotFoundInspection";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2000-2010 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed 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.jetbrains.idea.devkit.inspections;
|
||||
|
||||
import com.intellij.codeInspection.InspectionManager;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.ProblemDescriptor;
|
||||
import com.intellij.codeInspection.ProblemHighlightType;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleUtil;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ModuleRootManager;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.idea.devkit.inspections.quickfix.CreateHtmlDescriptionFix;
|
||||
import org.jetbrains.idea.devkit.util.PsiUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Konstantin Bulenkov
|
||||
*/
|
||||
public class IntentionDescriptionNotFoundInspection extends DevKitInspectionBase{
|
||||
@NonNls private static final String INTENTION = "com.intellij.codeInsight.intention.IntentionAction";
|
||||
@NonNls private static final String INSPECTION_DESCRIPTIONS = "intentionDescriptions";
|
||||
|
||||
@Override
|
||||
public ProblemDescriptor[] checkClass(@NotNull PsiClass aClass, @NotNull InspectionManager manager, boolean isOnTheFly) {
|
||||
final Project project = aClass.getProject();
|
||||
final PsiIdentifier nameIdentifier = aClass.getNameIdentifier();
|
||||
final Module module = ModuleUtil.findModuleForPsiElement(aClass);
|
||||
|
||||
if (nameIdentifier == null || module == null || !PsiUtil.isInstanciatable(aClass)) return null;
|
||||
|
||||
final PsiClass base = JavaPsiFacade.getInstance(project).findClass(INTENTION, GlobalSearchScope.allScope(project));
|
||||
|
||||
if (base == null || ! aClass.isInheritor(base, true)) return null;
|
||||
|
||||
final PsiMethod method = findNearestMethod("getFamilyName", aClass);
|
||||
if (method == null) return null;
|
||||
final String filename = PsiUtil.getReturnedLiteral(method, aClass);
|
||||
if (filename == null) return null;
|
||||
|
||||
for (PsiDirectory description : getIntentionDescriptionsDirs(module)) {
|
||||
PsiDirectory dir = description.findSubdirectory(filename);
|
||||
if (dir == null) dir = description.findSubdirectory(aClass.getName());
|
||||
if (dir == null) continue;
|
||||
final PsiFile descr = dir.findFile("description.html");
|
||||
if (descr != null) return null;
|
||||
}
|
||||
|
||||
|
||||
final PsiElement problem = aClass.getNameIdentifier();
|
||||
final ProblemDescriptor problemDescriptor = manager
|
||||
.createProblemDescriptor(problem == null ? nameIdentifier : problem,
|
||||
"Intention does not have a description", isOnTheFly, new LocalQuickFix[]{new CreateHtmlDescriptionFix(aClass.getName(), module, true)},
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
|
||||
return new ProblemDescriptor[]{problemDescriptor};
|
||||
}
|
||||
|
||||
public static List<VirtualFile> getPotentialRoots(Module module) {
|
||||
final PsiDirectory[] dirs = getIntentionDescriptionsDirs(module);
|
||||
final List<VirtualFile> result = new ArrayList<VirtualFile>();
|
||||
if (dirs.length != 0) {
|
||||
for (PsiDirectory dir : dirs) {
|
||||
final PsiDirectory parent = dir.getParentDirectory();
|
||||
if (parent != null) result.add(parent.getVirtualFile());
|
||||
}
|
||||
} else {
|
||||
result.addAll(Arrays.asList(ModuleRootManager.getInstance(module).getSourceRoots()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static PsiDirectory[] getIntentionDescriptionsDirs(Module module) {
|
||||
final PsiPackage aPackage = JavaPsiFacade.getInstance(module.getProject()).findPackage(INSPECTION_DESCRIPTIONS);
|
||||
if (aPackage != null) {
|
||||
return aPackage.getDirectories(GlobalSearchScope.moduleWithDependenciesScope(module));
|
||||
} else {
|
||||
return PsiDirectory.EMPTY_ARRAY;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiMethod findNearestMethod(String name, @Nullable PsiClass cls) {
|
||||
if (cls == null) return null;
|
||||
for (PsiMethod method : cls.getMethods()) {
|
||||
if (method.getParameterList().getParametersCount() == 0 && method.getName().equals(name)) {
|
||||
return method.getModifierList().hasModifierProperty(PsiModifier.ABSTRACT) ? null : method;
|
||||
}
|
||||
}
|
||||
return findNearestMethod(name, cls.getSuperClass());
|
||||
}
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
public String getDisplayName() {
|
||||
return "Intention Description Checker";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getShortName() {
|
||||
return "IntentionDescriptionNotFoundInspection";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledByDefault() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,8 @@ import com.intellij.util.ArrayUtil;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.idea.devkit.DevKitBundle;
|
||||
import org.jetbrains.idea.devkit.inspections.DescriptionNotFoundInspection;
|
||||
import org.jetbrains.idea.devkit.inspections.InspectionDescriptionNotFoundInspection;
|
||||
import org.jetbrains.idea.devkit.inspections.IntentionDescriptionNotFoundInspection;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.File;
|
||||
@@ -54,14 +55,15 @@ import java.util.List;
|
||||
*/
|
||||
public class CreateHtmlDescriptionFix implements LocalQuickFix, Iconable {
|
||||
private final String myFilename;
|
||||
private final Module myModule;
|
||||
@NonNls private static final String DESCRIPTIONS_FOLDER = "inspectionDescriptions";
|
||||
private final Module myModule;
|
||||
@NonNls private static final String TEMPLATE_NAME = "InspectionDescription.html";
|
||||
private static final Icon NEW_HTML_ICON = IconLoader.getIcon("/new_html.png");
|
||||
private final boolean isIntention;
|
||||
|
||||
public CreateHtmlDescriptionFix(String filename, Module module) {
|
||||
public CreateHtmlDescriptionFix(String filename, Module module, boolean isIntention) {
|
||||
myModule = module;
|
||||
myFilename = filename + ".html";
|
||||
this.isIntention = isIntention;
|
||||
myFilename = isIntention ? filename : filename + ".html";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -75,7 +77,10 @@ public class CreateHtmlDescriptionFix implements LocalQuickFix, Iconable {
|
||||
}
|
||||
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
final List<VirtualFile> virtualFiles = DescriptionNotFoundInspection.getPotentialRoots(myModule);
|
||||
final List<VirtualFile> virtualFiles = isIntention ?
|
||||
IntentionDescriptionNotFoundInspection.getPotentialRoots(myModule)
|
||||
:
|
||||
InspectionDescriptionNotFoundInspection.getPotentialRoots(myModule);
|
||||
final VirtualFile[] roots = prepare(VfsUtil.toVirtualFileArray(virtualFiles));
|
||||
if (roots.length == 1) {
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
@@ -88,7 +93,11 @@ public class CreateHtmlDescriptionFix implements LocalQuickFix, Iconable {
|
||||
else {
|
||||
List<String> options = new ArrayList<String>();
|
||||
for (VirtualFile file : roots) {
|
||||
options.add(file.getPresentableUrl() + File.separator + DESCRIPTIONS_FOLDER + File.separator + myFilename);
|
||||
String path = file.getPresentableUrl() + File.separator + getDescriptionFolderName() + File.separator + myFilename;
|
||||
if (isIntention) {
|
||||
path += File.separator + "description.html";
|
||||
}
|
||||
options.add(path);
|
||||
}
|
||||
final JList files = new JBList(ArrayUtil.toStringArray(options));
|
||||
final PopupChooserBuilder builder = JBPopupFactory.getInstance().createListPopupBuilder(files);
|
||||
@@ -117,16 +126,22 @@ public class CreateHtmlDescriptionFix implements LocalQuickFix, Iconable {
|
||||
PsiDirectory descrRoot = null;
|
||||
if (psiRoot == null) return;
|
||||
for (PsiDirectory dir : psiRoot.getSubdirectories()) {
|
||||
if (DESCRIPTIONS_FOLDER.equals(dir.getName())) {
|
||||
if (getDescriptionFolderName().equals(dir.getName())) {
|
||||
descrRoot = dir;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
descrRoot = descrRoot == null ? psiRoot.createSubdirectory(DESCRIPTIONS_FOLDER) : descrRoot;
|
||||
descrRoot = descrRoot == null ? psiRoot.createSubdirectory(getDescriptionFolderName()) : descrRoot;
|
||||
if (isIntention) {
|
||||
PsiDirectory dir = descrRoot.findSubdirectory(myFilename);
|
||||
if (dir == null) {
|
||||
descrRoot = descrRoot.createSubdirectory(myFilename);
|
||||
}
|
||||
}
|
||||
final FileTemplate descrTemplate = FileTemplateManager.getInstance().getJ2eeTemplate(TEMPLATE_NAME);
|
||||
final PsiElement template = FileTemplateUtil.createFromTemplate(descrTemplate, myFilename, null, descrRoot);
|
||||
final PsiElement template = FileTemplateUtil.createFromTemplate(descrTemplate, isIntention? "description.html" : myFilename, null, descrRoot);
|
||||
if (template instanceof PsiFile) {
|
||||
final VirtualFile file = ((PsiFile)template).getVirtualFile();
|
||||
if (file != null) {
|
||||
@@ -142,7 +157,7 @@ public class CreateHtmlDescriptionFix implements LocalQuickFix, Iconable {
|
||||
return NEW_HTML_ICON;
|
||||
}
|
||||
|
||||
private static VirtualFile[] prepare(VirtualFile[] roots) {
|
||||
private VirtualFile[] prepare(VirtualFile[] roots) {
|
||||
List<VirtualFile> found = new ArrayList<VirtualFile>();
|
||||
for (VirtualFile root : roots) {
|
||||
if (containsDescriptionDir(root)) {
|
||||
@@ -152,13 +167,17 @@ public class CreateHtmlDescriptionFix implements LocalQuickFix, Iconable {
|
||||
return found.size() > 0 ? VfsUtil.toVirtualFileArray(found) : roots;
|
||||
}
|
||||
|
||||
private static boolean containsDescriptionDir(VirtualFile root) {
|
||||
private boolean containsDescriptionDir(VirtualFile root) {
|
||||
if (!root.isDirectory()) return false;
|
||||
for (VirtualFile file : root.getChildren()) {
|
||||
if (file.isDirectory() && DESCRIPTIONS_FOLDER.equals(file.getName())) {
|
||||
if (file.isDirectory() && getDescriptionFolderName().equals(file.getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String getDescriptionFolderName() {
|
||||
return isIntention ? "intentionDescriptions" : "inspectionDescriptions";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user