mirror of
https://github.com/SuspiciousActivity/ByteEdit.git
synced 2026-08-13 17:56:17 +00:00
Fixes and updates
This commit is contained in:
1 parent
d1710ea6a5
commit
fb4da77359
211 files changed
+45363
-45571
No files matched your search
@@ -46,7 +46,7 @@ import org.fife.ui.rtextarea.RTextAreaHighlighter.HighlightInfo;
|
||||
* @version 0.9
|
||||
*/
|
||||
class ParserManager implements DocumentListener, ActionListener, HyperlinkListener, PropertyChangeListener {
|
||||
|
||||
|
||||
private RSyntaxTextArea textArea;
|
||||
private List<Parser> parsers;
|
||||
private Timer timer;
|
||||
@@ -55,20 +55,21 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
private Position firstOffsetModded;
|
||||
private Position lastOffsetModded;
|
||||
/**
|
||||
* Mapping of notices to their highlights in the editor. Can't use a Map
|
||||
* since parsers could return two <code>ParserNotice</code>s that compare
|
||||
* equally via <code>equals()</code>. Real-world example: The Perl
|
||||
* compiler will return 2+ identical error messages if the same error is
|
||||
* committed in a single line more than once.
|
||||
* Mapping of notices to their highlights in the editor. Can't use a Map since
|
||||
* parsers could return two <code>ParserNotice</code>s that compare equally via
|
||||
* <code>equals()</code>. Real-world example: The Perl compiler will return 2+
|
||||
* identical error messages if the same error is committed in a single line more
|
||||
* than once.
|
||||
*/
|
||||
private List<NoticeHighlightPair> noticeHighlightPairs;
|
||||
/**
|
||||
* Painter used to underline errors.
|
||||
*/
|
||||
private SquiggleUnderlineHighlightPainter parserErrorHighlightPainter = new SquiggleUnderlineHighlightPainter(Color.RED);
|
||||
private SquiggleUnderlineHighlightPainter parserErrorHighlightPainter = new SquiggleUnderlineHighlightPainter(
|
||||
Color.RED);
|
||||
/**
|
||||
* If this system property is set to <code>true</code>, debug messages
|
||||
* will be printed to stdout to help diagnose parsing issues.
|
||||
* If this system property is set to <code>true</code>, debug messages will be
|
||||
* printed to stdout to help diagnose parsing issues.
|
||||
*/
|
||||
private static final String PROPERTY_DEBUG_PARSING = "rsta.debugParsing";
|
||||
/**
|
||||
@@ -76,31 +77,26 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
*/
|
||||
private static final boolean DEBUG_PARSING;
|
||||
/**
|
||||
* The default delay between the last key press and when the document
|
||||
* is parsed, in milliseconds.
|
||||
* The default delay between the last key press and when the document is parsed,
|
||||
* in milliseconds.
|
||||
*/
|
||||
private static final int DEFAULT_DELAY_MS = 1250;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param textArea
|
||||
* The text area whose document the parser will be
|
||||
* parsing.
|
||||
* @param textArea The text area whose document the parser will be parsing.
|
||||
*/
|
||||
ParserManager(RSyntaxTextArea textArea) {
|
||||
this(DEFAULT_DELAY_MS, textArea);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param delay
|
||||
* The delay between the last key press and when the document
|
||||
* is parsed.
|
||||
* @param textArea
|
||||
* The text area whose document the parser will be
|
||||
* parsing.
|
||||
* @param delay The delay between the last key press and when the document is
|
||||
* parsed.
|
||||
* @param textArea The text area whose document the parser will be parsing.
|
||||
*/
|
||||
ParserManager(int delay, RSyntaxTextArea textArea) {
|
||||
this.textArea = textArea;
|
||||
@@ -111,12 +107,11 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
timer.setRepeats(false);
|
||||
running = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the timer fires (e.g. it's time to parse the document).
|
||||
*
|
||||
* @param e
|
||||
* The event.
|
||||
* @param e The event.
|
||||
*/
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
@@ -132,7 +127,8 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
RSyntaxDocument doc = (RSyntaxDocument) textArea.getDocument();
|
||||
Element root = doc.getDefaultRootElement();
|
||||
int firstLine = firstOffsetModded == null ? 0 : root.getElementIndex(firstOffsetModded.getOffset());
|
||||
int lastLine = lastOffsetModded == null ? root.getElementCount() - 1 : root.getElementIndex(lastOffsetModded.getOffset());
|
||||
int lastLine = lastOffsetModded == null ? root.getElementCount() - 1
|
||||
: root.getElementIndex(lastOffsetModded.getOffset());
|
||||
firstOffsetModded = lastOffsetModded = null;
|
||||
if (DEBUG_PARSING) {
|
||||
System.out.println("[DEBUG]: Minimum lines to parse: " + firstLine + "-" + lastLine);
|
||||
@@ -158,13 +154,11 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
System.out.println("Total parsing time: " + time + " seconds");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a parser for the text area.
|
||||
*
|
||||
* @param parser
|
||||
* The new parser. If this is <code>null</code>, nothing
|
||||
* happens.
|
||||
* @param parser The new parser. If this is <code>null</code>, nothing happens.
|
||||
* @see #getParser(int)
|
||||
* @see #removeParser(Parser)
|
||||
*/
|
||||
@@ -183,13 +177,12 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds highlights for a list of parser notices. Any current notices
|
||||
* from the same Parser, in the same parsed range, are removed.
|
||||
* Adds highlights for a list of parser notices. Any current notices from the
|
||||
* same Parser, in the same parsed range, are removed.
|
||||
*
|
||||
* @param res
|
||||
* The result of a parsing.
|
||||
* @param res The result of a parsing.
|
||||
* @see #clearParserNoticeHighlights()
|
||||
*/
|
||||
private void addParserNoticeHighlights(ParseResult res) {
|
||||
@@ -227,16 +220,16 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
System.out.println("[DEBUG]: Done adding parser notices from " + res.getParser());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the document is modified.
|
||||
*
|
||||
* @param e
|
||||
* The document event.
|
||||
* @param e The document event.
|
||||
*/
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {}
|
||||
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
}
|
||||
|
||||
private void clearParserNoticeHighlights() {
|
||||
RSyntaxTextAreaHighlighter h = (RSyntaxTextAreaHighlighter) textArea.getHighlighter();
|
||||
if (h != null) {
|
||||
@@ -246,12 +239,11 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
noticeHighlightPairs.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all parser notice highlights for a specific parser.
|
||||
*
|
||||
* @param parser
|
||||
* The parser whose highlights to remove.
|
||||
* @param parser The parser whose highlights to remove.
|
||||
*/
|
||||
private void clearParserNoticeHighlights(Parser parser) {
|
||||
RSyntaxTextAreaHighlighter h = (RSyntaxTextAreaHighlighter) textArea.getHighlighter();
|
||||
@@ -268,7 +260,7 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all parsers and any highlights they have created.
|
||||
*
|
||||
@@ -280,20 +272,18 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
parsers.clear();
|
||||
textArea.fireParserNoticesChange();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Forces the given {@link Parser} to re-parse the content of this text
|
||||
* area.
|
||||
* Forces the given {@link Parser} to re-parse the content of this text area.
|
||||
* <p>
|
||||
*
|
||||
* This method can be useful when a <code>Parser</code> can be configured
|
||||
* as to what notices it returns. For example, if a Java language parser
|
||||
* can be configured to set whether no serialVersionUID is a warning,
|
||||
* error, or ignored, this method can be called after changing the expected
|
||||
* notice type to have the document re-parsed.
|
||||
* This method can be useful when a <code>Parser</code> can be configured as to
|
||||
* what notices it returns. For example, if a Java language parser can be
|
||||
* configured to set whether no serialVersionUID is a warning, error, or
|
||||
* ignored, this method can be called after changing the expected notice type to
|
||||
* have the document re-parsed.
|
||||
*
|
||||
* @param parser
|
||||
* The index of the <code>Parser</code> to re-run.
|
||||
* @param parser The index of the <code>Parser</code> to re-run.
|
||||
* @see #getParser(int)
|
||||
*/
|
||||
public void forceReparsing(int parser) {
|
||||
@@ -313,10 +303,10 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
doc.readUnlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the delay between the last "concurrent" edit and when the
|
||||
* document is re-parsed.
|
||||
* Returns the delay between the last "concurrent" edit and when the document is
|
||||
* re-parsed.
|
||||
*
|
||||
* @return The delay, in milliseconds.
|
||||
* @see #setDelay(int)
|
||||
@@ -324,12 +314,11 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
public int getDelay() {
|
||||
return timer.getDelay();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the specified parser.
|
||||
*
|
||||
* @param index
|
||||
* The index of the parser.
|
||||
* @param index The index of the parser.
|
||||
* @return The parser.
|
||||
* @see #getParserCount()
|
||||
* @see #addParser(Parser)
|
||||
@@ -338,7 +327,7 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
public Parser getParser(int index) {
|
||||
return parsers.get(index);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of registered parsers.
|
||||
*
|
||||
@@ -347,14 +336,12 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
public int getParserCount() {
|
||||
return parsers.size();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a list of the current parser notices for this text area.
|
||||
* This method (like most Swing methods) should only be called on the
|
||||
* EDT.
|
||||
* Returns a list of the current parser notices for this text area. This method
|
||||
* (like most Swing methods) should only be called on the EDT.
|
||||
*
|
||||
* @return The list of notices. This will be an empty list if there are
|
||||
* none.
|
||||
* @return The list of notices. This will be an empty list if there are none.
|
||||
*/
|
||||
public List<ParserNotice> getParserNotices() {
|
||||
List<ParserNotice> notices = new ArrayList<ParserNotice>();
|
||||
@@ -365,15 +352,13 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
}
|
||||
return notices;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the tool tip to display for a mouse event at the given
|
||||
* location. This method is overridden to give a registered parser a
|
||||
* chance to display a tool tip (such as an error description when the
|
||||
* mouse is over an error highlight).
|
||||
* Returns the tool tip to display for a mouse event at the given location. This
|
||||
* method is overridden to give a registered parser a chance to display a tool
|
||||
* tip (such as an error description when the mouse is over an error highlight).
|
||||
*
|
||||
* @param e
|
||||
* The mouse event.
|
||||
* @param e The mouse event.
|
||||
* @return The tool tip to display, and possibly a hyperlink event handler.
|
||||
*/
|
||||
public ToolTipInfo getToolTipText(MouseEvent e) {
|
||||
@@ -385,19 +370,12 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
int pos = textArea.viewToModel(p);
|
||||
/*
|
||||
* Highlighter.Highlight[] highlights = textArea.getHighlighter().
|
||||
* getHighlights();
|
||||
* for (int i=0; i<highlights.length; i++) {
|
||||
* Highlighter.Highlight h = highlights[i];
|
||||
* //if (h instanceof ParserNoticeHighlight) {
|
||||
* // ParserNoticeHighlight pnh = (ParserNoticeHighlight)h;
|
||||
* int start = h.getStartOffset();
|
||||
* int end = h.getEndOffset();
|
||||
* if (start<=pos && end>=pos) {
|
||||
* //return pnh.getMessage();
|
||||
* return textArea.getText(start, end-start);
|
||||
* }
|
||||
* //}
|
||||
* }
|
||||
* getHighlights(); for (int i=0; i<highlights.length; i++) {
|
||||
* Highlighter.Highlight h = highlights[i]; //if (h instanceof
|
||||
* ParserNoticeHighlight) { // ParserNoticeHighlight pnh =
|
||||
* (ParserNoticeHighlight)h; int start = h.getStartOffset(); int end =
|
||||
* h.getEndOffset(); if (start<=pos && end>=pos) { //return pnh.getMessage();
|
||||
* return textArea.getText(start, end-start); } //} }
|
||||
*/
|
||||
if (noticeHighlightPairs != null) {
|
||||
for (NoticeHighlightPair pair : noticeHighlightPairs) {
|
||||
@@ -418,25 +396,23 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
URL imageBase = parserForTip == null ? null : parserForTip.getImageBase();
|
||||
return new ToolTipInfo(tip, listener, imageBase);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the document is modified.
|
||||
*
|
||||
* @param e
|
||||
* The document event.
|
||||
* @param e The document event.
|
||||
*/
|
||||
public void handleDocumentEvent(DocumentEvent e) {
|
||||
if (running && parsers.size() > 0) {
|
||||
timer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the user clicks a hyperlink in a
|
||||
* {@link org.fife.ui.rsyntaxtextarea.focusabletip.FocusableTip}.
|
||||
*
|
||||
* @param e
|
||||
* The event.
|
||||
* @param e The event.
|
||||
*/
|
||||
@Override
|
||||
public void hyperlinkUpdate(HyperlinkEvent e) {
|
||||
@@ -444,12 +420,11 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
parserForTip.getHyperlinkListener().linkClicked(textArea, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the document is modified.
|
||||
*
|
||||
* @param e
|
||||
* The document event.
|
||||
* @param e The document event.
|
||||
*/
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
@@ -469,14 +444,12 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
}
|
||||
handleDocumentEvent(e);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether a parser notice contains the specified offset.
|
||||
*
|
||||
* @param notice
|
||||
* The notice.
|
||||
* @param offs
|
||||
* The offset.
|
||||
* @param notice The notice.
|
||||
* @param offs The offset.
|
||||
* @return Whether the notice contains the offset.
|
||||
*/
|
||||
private boolean noticeContainsPosition(ParserNotice notice, int offs) {
|
||||
@@ -492,20 +465,18 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
Element elem = root.getElement(line);
|
||||
return elem != null && offs >= elem.getStartOffset() && offs < elem.getEndOffset();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Since <code>viewToModel()</code> returns the <em>closest</em> model
|
||||
* position, and the position doesn't <em>necessarily</em> contain the
|
||||
* point passed in as an argument, this method checks whether the point is
|
||||
* indeed contained in the view rectangle for the specified offset.
|
||||
* Since <code>viewToModel()</code> returns the <em>closest</em> model position,
|
||||
* and the position doesn't <em>necessarily</em> contain the point passed in as
|
||||
* an argument, this method checks whether the point is indeed contained in the
|
||||
* view rectangle for the specified offset.
|
||||
*
|
||||
* @param notice
|
||||
* The parser notice.
|
||||
* @param p
|
||||
* The point possibly contained in the view range of the
|
||||
* parser notice.
|
||||
* @return Whether the parser notice actually contains the specified point
|
||||
* in the view.
|
||||
* @param notice The parser notice.
|
||||
* @param p The point possibly contained in the view range of the parser
|
||||
* notice.
|
||||
* @return Whether the parser notice actually contains the specified point in
|
||||
* the view.
|
||||
*/
|
||||
private boolean noticeContainsPointInView(ParserNotice notice, Point p) {
|
||||
try {
|
||||
@@ -543,12 +514,11 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when a property we're interested in changes.
|
||||
*
|
||||
* @param e
|
||||
* The property change event.
|
||||
* @param e The property change event.
|
||||
*/
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent e) {
|
||||
@@ -565,12 +535,11 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes a parser.
|
||||
*
|
||||
* @param parser
|
||||
* The parser to remove.
|
||||
* @param parser The parser to remove.
|
||||
* @return Whether the parser was found.
|
||||
* @see #addParser(Parser)
|
||||
* @see #getParser(int)
|
||||
@@ -583,13 +552,12 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all parser notices (and clears highlights in the editor) from
|
||||
* a particular parser.
|
||||
* Removes all parser notices (and clears highlights in the editor) from a
|
||||
* particular parser.
|
||||
*
|
||||
* @param parser
|
||||
* The parser.
|
||||
* @param parser The parser.
|
||||
*/
|
||||
private void removeParserNotices(Parser parser) {
|
||||
if (noticeHighlightPairs != null) {
|
||||
@@ -603,14 +571,13 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes any currently stored notices (and the corresponding highlights
|
||||
* from the editor) from the same Parser, and in the given line range,
|
||||
* as in the results.
|
||||
* Removes any currently stored notices (and the corresponding highlights from
|
||||
* the editor) from the same Parser, and in the given line range, as in the
|
||||
* results.
|
||||
*
|
||||
* @param res
|
||||
* The results.
|
||||
* @param res The results.
|
||||
*/
|
||||
private void removeParserNotices(ParseResult res) {
|
||||
if (noticeHighlightPairs != null) {
|
||||
@@ -632,12 +599,11 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the document is modified.
|
||||
*
|
||||
* @param e
|
||||
* The document event.
|
||||
* @param e The document event.
|
||||
*/
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
@@ -658,7 +624,7 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
}
|
||||
handleDocumentEvent(e);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Restarts parsing the document.
|
||||
*
|
||||
@@ -668,14 +634,13 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
timer.restart();
|
||||
running = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the delay between the last "concurrent" edit and when the document
|
||||
* is re-parsed.
|
||||
* Sets the delay between the last "concurrent" edit and when the document is
|
||||
* re-parsed.
|
||||
*
|
||||
* @param millis
|
||||
* The new delay, in milliseconds. This must be greater
|
||||
* than <code>0</code>.
|
||||
* @param millis The new delay, in milliseconds. This must be greater than
|
||||
* <code>0</code>.
|
||||
* @see #getDelay()
|
||||
*/
|
||||
public void setDelay(int millis) {
|
||||
@@ -688,20 +653,18 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
timer.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether a parser notice should be removed, based on a parse
|
||||
* result.
|
||||
* Returns whether a parser notice should be removed, based on a parse result.
|
||||
*
|
||||
* @param notice
|
||||
* The notice in question.
|
||||
* @param res
|
||||
* The result.
|
||||
* @param notice The notice in question.
|
||||
* @param res The result.
|
||||
* @return Whether the notice should be removed.
|
||||
*/
|
||||
private boolean shouldRemoveNotice(ParserNotice notice, ParseResult res) {
|
||||
if (DEBUG_PARSING) {
|
||||
System.out.println("[DEBUG]: ... ... shouldRemoveNotice " + notice + ": " + (notice.getParser() == res.getParser()));
|
||||
System.out.println(
|
||||
"[DEBUG]: ... ... shouldRemoveNotice " + notice + ": " + (notice.getParser() == res.getParser()));
|
||||
}
|
||||
// NOTE: We must currently remove all notices for the parser. Parser
|
||||
// implementors are required to parse the entire document each parsing
|
||||
@@ -712,7 +675,7 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
// return same_parser && (in_reparsed_range || in_deleted_end_of_doc)
|
||||
return notice.getParser() == res.getParser();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stops parsing the document.
|
||||
*
|
||||
@@ -722,21 +685,21 @@ class ParserManager implements DocumentListener, ActionListener, HyperlinkListen
|
||||
timer.stop();
|
||||
running = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mapping of a parser notice to its highlight in the editor.
|
||||
*/
|
||||
private static class NoticeHighlightPair {
|
||||
|
||||
|
||||
private ParserNotice notice;
|
||||
private HighlightInfo highlight;
|
||||
|
||||
|
||||
NoticeHighlightPair(ParserNotice notice, HighlightInfo highlight) {
|
||||
this.notice = notice;
|
||||
this.highlight = highlight;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static {
|
||||
boolean debugParsing = false;
|
||||
try {
|
||||
|
||||
Reference in new issue
Block a user