mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-30 10:45:18 +00:00
Highlight closure captures when cursor is on pipe
This commit is contained in:
parent
9c0c13ec8e
commit
a64626d99e
@ -115,9 +115,10 @@ impl InferenceContext<'_> {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub(crate) struct HirPlace {
|
pub(crate) struct HirPlace {
|
||||||
pub(crate) local: BindingId,
|
pub local: BindingId,
|
||||||
pub(crate) projections: Vec<ProjectionElem<Infallible, Ty>>,
|
pub(crate) projections: Vec<ProjectionElem<Infallible, Ty>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HirPlace {
|
impl HirPlace {
|
||||||
fn ty(&self, ctx: &mut InferenceContext<'_>) -> Ty {
|
fn ty(&self, ctx: &mut InferenceContext<'_>) -> Ty {
|
||||||
let mut ty = ctx.table.resolve_completely(ctx.result[self.local].clone());
|
let mut ty = ctx.table.resolve_completely(ctx.result[self.local].clone());
|
||||||
@ -161,6 +162,10 @@ pub struct CapturedItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CapturedItem {
|
impl CapturedItem {
|
||||||
|
pub fn local(&self) -> BindingId {
|
||||||
|
self.place.local
|
||||||
|
}
|
||||||
|
|
||||||
pub fn display_kind(&self) -> &'static str {
|
pub fn display_kind(&self) -> &'static str {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
CaptureKind::ByRef(k) => match k {
|
CaptureKind::ByRef(k) => match k {
|
||||||
|
@ -3209,11 +3209,11 @@ impl Closure {
|
|||||||
self.clone().as_ty().display(db).with_closure_style(ClosureStyle::ImplFn).to_string()
|
self.clone().as_ty().display(db).with_closure_style(ClosureStyle::ImplFn).to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn captured_items(&self, db: &dyn HirDatabase) -> Vec<hir_ty::CapturedItem> {
|
pub fn captured_items(&self, db: &dyn HirDatabase) -> Vec<ClosureCapture> {
|
||||||
let owner = db.lookup_intern_closure((self.id).into()).0;
|
let owner = db.lookup_intern_closure((self.id).into()).0;
|
||||||
let infer = &db.infer(owner);
|
let infer = &db.infer(owner);
|
||||||
let info = infer.closure_info(&self.id);
|
let info = infer.closure_info(&self.id);
|
||||||
info.0.clone()
|
info.0.iter().cloned().map(|capture| ClosureCapture { owner, capture }).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fn_trait(&self, db: &dyn HirDatabase) -> FnTrait {
|
pub fn fn_trait(&self, db: &dyn HirDatabase) -> FnTrait {
|
||||||
@ -3224,6 +3224,26 @@ impl Closure {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ClosureCapture {
|
||||||
|
owner: DefWithBodyId,
|
||||||
|
capture: hir_ty::CapturedItem,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClosureCapture {
|
||||||
|
pub fn local(&self) -> Local {
|
||||||
|
Local { parent: self.owner, binding_id: self.capture.local() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_kind(&self) -> &'static str {
|
||||||
|
self.capture.display_kind()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_place(&self, owner: ClosureId, db: &dyn HirDatabase) -> String {
|
||||||
|
self.capture.display_place(owner, db)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct Type {
|
pub struct Type {
|
||||||
env: Arc<TraitEnvironment>,
|
env: Arc<TraitEnvironment>,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use hir::Semantics;
|
use hir::Semantics;
|
||||||
use ide_db::{
|
use ide_db::{
|
||||||
base_db::{FileId, FilePosition},
|
base_db::{FileId, FilePosition, FileRange},
|
||||||
defs::{Definition, IdentClass},
|
defs::{Definition, IdentClass},
|
||||||
helpers::pick_best_token,
|
helpers::pick_best_token,
|
||||||
search::{FileReference, ReferenceCategory, SearchScope},
|
search::{FileReference, ReferenceCategory, SearchScope},
|
||||||
@ -30,6 +30,7 @@ pub struct HighlightRelatedConfig {
|
|||||||
pub references: bool,
|
pub references: bool,
|
||||||
pub exit_points: bool,
|
pub exit_points: bool,
|
||||||
pub break_points: bool,
|
pub break_points: bool,
|
||||||
|
pub closure_captures: bool,
|
||||||
pub yield_points: bool,
|
pub yield_points: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,11 +54,12 @@ pub(crate) fn highlight_related(
|
|||||||
|
|
||||||
let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {
|
let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {
|
||||||
T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?`
|
T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?`
|
||||||
T![->] => 3,
|
T![->] | T![|] => 3,
|
||||||
kind if kind.is_keyword() => 2,
|
kind if kind.is_keyword() => 2,
|
||||||
IDENT | INT_NUMBER => 1,
|
IDENT | INT_NUMBER => 1,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
})?;
|
})?;
|
||||||
|
// most if not all of these should be re-implemented with information seeded from hir
|
||||||
match token.kind() {
|
match token.kind() {
|
||||||
T![?] if config.exit_points && token.parent().and_then(ast::TryExpr::cast).is_some() => {
|
T![?] if config.exit_points && token.parent().and_then(ast::TryExpr::cast).is_some() => {
|
||||||
highlight_exit_points(sema, token)
|
highlight_exit_points(sema, token)
|
||||||
@ -70,11 +72,64 @@ pub(crate) fn highlight_related(
|
|||||||
T![break] | T![loop] | T![while] | T![continue] if config.break_points => {
|
T![break] | T![loop] | T![while] | T![continue] if config.break_points => {
|
||||||
highlight_break_points(token)
|
highlight_break_points(token)
|
||||||
}
|
}
|
||||||
|
T![|] if config.closure_captures => highlight_closure_captures(
|
||||||
|
sema,
|
||||||
|
token.parent_ancestors().nth(1).and_then(ast::ClosureExpr::cast)?,
|
||||||
|
file_id,
|
||||||
|
),
|
||||||
|
T![move] if config.closure_captures => highlight_closure_captures(
|
||||||
|
sema,
|
||||||
|
token.parent().and_then(ast::ClosureExpr::cast)?,
|
||||||
|
file_id,
|
||||||
|
),
|
||||||
_ if config.references => highlight_references(sema, &syntax, token, file_id),
|
_ if config.references => highlight_references(sema, &syntax, token, file_id),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn highlight_closure_captures(
|
||||||
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
|
node: ast::ClosureExpr,
|
||||||
|
file_id: FileId,
|
||||||
|
) -> Option<Vec<HighlightedRange>> {
|
||||||
|
let search_range = node.body()?.syntax().text_range();
|
||||||
|
let ty = &sema.type_of_expr(&node.into())?.original;
|
||||||
|
let c = ty.as_closure()?;
|
||||||
|
Some(
|
||||||
|
c.captured_items(sema.db)
|
||||||
|
.into_iter()
|
||||||
|
.map(|capture| capture.local())
|
||||||
|
.flat_map(|local| {
|
||||||
|
let usages = Definition::Local(local)
|
||||||
|
.usages(sema)
|
||||||
|
.set_scope(Some(SearchScope::file_range(FileRange {
|
||||||
|
file_id,
|
||||||
|
range: search_range,
|
||||||
|
})))
|
||||||
|
.include_self_refs()
|
||||||
|
.all()
|
||||||
|
.references
|
||||||
|
.remove(&file_id)
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|FileReference { category, range, .. }| HighlightedRange {
|
||||||
|
range,
|
||||||
|
category,
|
||||||
|
});
|
||||||
|
let category = local.is_mut(sema.db).then_some(ReferenceCategory::Write);
|
||||||
|
local
|
||||||
|
.sources(sema.db)
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| x.to_nav(sema.db))
|
||||||
|
.filter(|decl| decl.file_id == file_id)
|
||||||
|
.filter_map(|decl| decl.focus_range)
|
||||||
|
.map(move |range| HighlightedRange { range, category })
|
||||||
|
.chain(usages)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn highlight_references(
|
fn highlight_references(
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
node: &SyntaxNode,
|
node: &SyntaxNode,
|
||||||
@ -93,10 +148,7 @@ fn highlight_references(
|
|||||||
.remove(&file_id)
|
.remove(&file_id)
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(|FileReference { category: access, range, .. }| HighlightedRange {
|
.map(|FileReference { category, range, .. }| HighlightedRange { range, category });
|
||||||
range,
|
|
||||||
category: access,
|
|
||||||
});
|
|
||||||
let mut res = FxHashSet::default();
|
let mut res = FxHashSet::default();
|
||||||
for &def in &defs {
|
for &def in &defs {
|
||||||
match def {
|
match def {
|
||||||
@ -352,16 +404,17 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
const ENABLED_CONFIG: HighlightRelatedConfig = HighlightRelatedConfig {
|
||||||
|
break_points: true,
|
||||||
|
exit_points: true,
|
||||||
|
references: true,
|
||||||
|
closure_captures: true,
|
||||||
|
yield_points: true,
|
||||||
|
};
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn check(ra_fixture: &str) {
|
fn check(ra_fixture: &str) {
|
||||||
let config = HighlightRelatedConfig {
|
check_with_config(ra_fixture, ENABLED_CONFIG);
|
||||||
break_points: true,
|
|
||||||
exit_points: true,
|
|
||||||
references: true,
|
|
||||||
yield_points: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
check_with_config(ra_fixture, config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
@ -1086,12 +1139,7 @@ fn function(field: u32) {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hl_disabled_ref_local() {
|
fn test_hl_disabled_ref_local() {
|
||||||
let config = HighlightRelatedConfig {
|
let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
|
||||||
references: false,
|
|
||||||
break_points: true,
|
|
||||||
exit_points: true,
|
|
||||||
yield_points: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
check_with_config(
|
check_with_config(
|
||||||
r#"
|
r#"
|
||||||
@ -1106,12 +1154,7 @@ fn foo() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hl_disabled_ref_local_preserved_break() {
|
fn test_hl_disabled_ref_local_preserved_break() {
|
||||||
let config = HighlightRelatedConfig {
|
let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
|
||||||
references: false,
|
|
||||||
break_points: true,
|
|
||||||
exit_points: true,
|
|
||||||
yield_points: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
check_with_config(
|
check_with_config(
|
||||||
r#"
|
r#"
|
||||||
@ -1146,12 +1189,7 @@ fn foo() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hl_disabled_ref_local_preserved_yield() {
|
fn test_hl_disabled_ref_local_preserved_yield() {
|
||||||
let config = HighlightRelatedConfig {
|
let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
|
||||||
references: false,
|
|
||||||
break_points: true,
|
|
||||||
exit_points: true,
|
|
||||||
yield_points: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
check_with_config(
|
check_with_config(
|
||||||
r#"
|
r#"
|
||||||
@ -1182,12 +1220,7 @@ async fn foo() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hl_disabled_ref_local_preserved_exit() {
|
fn test_hl_disabled_ref_local_preserved_exit() {
|
||||||
let config = HighlightRelatedConfig {
|
let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
|
||||||
references: false,
|
|
||||||
break_points: true,
|
|
||||||
exit_points: true,
|
|
||||||
yield_points: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
check_with_config(
|
check_with_config(
|
||||||
r#"
|
r#"
|
||||||
@ -1225,12 +1258,7 @@ fn foo() ->$0 i32 {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hl_disabled_break() {
|
fn test_hl_disabled_break() {
|
||||||
let config = HighlightRelatedConfig {
|
let config = HighlightRelatedConfig { break_points: false, ..ENABLED_CONFIG };
|
||||||
references: true,
|
|
||||||
break_points: false,
|
|
||||||
exit_points: true,
|
|
||||||
yield_points: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
check_with_config(
|
check_with_config(
|
||||||
r#"
|
r#"
|
||||||
@ -1246,12 +1274,7 @@ fn foo() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hl_disabled_yield() {
|
fn test_hl_disabled_yield() {
|
||||||
let config = HighlightRelatedConfig {
|
let config = HighlightRelatedConfig { yield_points: false, ..ENABLED_CONFIG };
|
||||||
references: true,
|
|
||||||
break_points: true,
|
|
||||||
exit_points: true,
|
|
||||||
yield_points: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
check_with_config(
|
check_with_config(
|
||||||
r#"
|
r#"
|
||||||
@ -1265,12 +1288,7 @@ async$0 fn foo() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hl_disabled_exit() {
|
fn test_hl_disabled_exit() {
|
||||||
let config = HighlightRelatedConfig {
|
let config = HighlightRelatedConfig { exit_points: false, ..ENABLED_CONFIG };
|
||||||
references: true,
|
|
||||||
break_points: true,
|
|
||||||
exit_points: false,
|
|
||||||
yield_points: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
check_with_config(
|
check_with_config(
|
||||||
r#"
|
r#"
|
||||||
@ -1411,6 +1429,34 @@ impl Trait for () {
|
|||||||
type Output$0 = ();
|
type Output$0 = ();
|
||||||
// ^^^^^^
|
// ^^^^^^
|
||||||
}
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_closure_capture_pipe() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn f() {
|
||||||
|
let x = 1;
|
||||||
|
// ^
|
||||||
|
let c = $0|y| x + y;
|
||||||
|
// ^ read
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_closure_capture_move() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn f() {
|
||||||
|
let x = 1;
|
||||||
|
// ^
|
||||||
|
let c = move$0 |y| x + y;
|
||||||
|
// ^ read
|
||||||
|
}
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -281,6 +281,8 @@ config_data! {
|
|||||||
|
|
||||||
/// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
|
/// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
|
||||||
highlightRelated_breakPoints_enable: bool = "true",
|
highlightRelated_breakPoints_enable: bool = "true",
|
||||||
|
/// Enables highlighting of all captures of a closure while the cursor is on the `|` or move keyword of a closure.
|
||||||
|
highlightRelated_closureCaptures_enable: bool = "true",
|
||||||
/// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).
|
/// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).
|
||||||
highlightRelated_exitPoints_enable: bool = "true",
|
highlightRelated_exitPoints_enable: bool = "true",
|
||||||
/// Enables highlighting of related references while the cursor is on any identifier.
|
/// Enables highlighting of related references while the cursor is on any identifier.
|
||||||
@ -1554,6 +1556,7 @@ impl Config {
|
|||||||
break_points: self.data.highlightRelated_breakPoints_enable,
|
break_points: self.data.highlightRelated_breakPoints_enable,
|
||||||
exit_points: self.data.highlightRelated_exitPoints_enable,
|
exit_points: self.data.highlightRelated_exitPoints_enable,
|
||||||
yield_points: self.data.highlightRelated_yieldPoints_enable,
|
yield_points: self.data.highlightRelated_yieldPoints_enable,
|
||||||
|
closure_captures: self.data.highlightRelated_closureCaptures_enable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,7 +434,7 @@ pub enum HoverRequest {}
|
|||||||
impl Request for HoverRequest {
|
impl Request for HoverRequest {
|
||||||
type Params = HoverParams;
|
type Params = HoverParams;
|
||||||
type Result = Option<Hover>;
|
type Result = Option<Hover>;
|
||||||
const METHOD: &'static str = "textDocument/hover";
|
const METHOD: &'static str = lsp_types::request::HoverRequest::METHOD;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||||
|
@ -95,7 +95,7 @@ fn try_extract_range(text: &str) -> Option<(TextRange, String)> {
|
|||||||
Some((TextRange::new(start, end), text))
|
Some((TextRange::new(start, end), text))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum RangeOrOffset {
|
pub enum RangeOrOffset {
|
||||||
Range(TextRange),
|
Range(TextRange),
|
||||||
Offset(TextSize),
|
Offset(TextSize),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!---
|
<!---
|
||||||
lsp_ext.rs hash: 37ac44a0f507e05a
|
lsp_ext.rs hash: 31ca513a249753ab
|
||||||
|
|
||||||
If you need to change the above hash to make the test pass, please check if you
|
If you need to change the above hash to make the test pass, please check if you
|
||||||
need to adjust this doc as well and ping this issue:
|
need to adjust this doc as well and ping this issue:
|
||||||
|
@ -352,6 +352,11 @@ Controls file watching implementation.
|
|||||||
--
|
--
|
||||||
Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
|
Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
|
||||||
--
|
--
|
||||||
|
[[rust-analyzer.highlightRelated.closureCaptures.enable]]rust-analyzer.highlightRelated.closureCaptures.enable (default: `true`)::
|
||||||
|
+
|
||||||
|
--
|
||||||
|
Enables highlighting of all captures of a closure while the cursor is on the `|` or move keyword of a closure.
|
||||||
|
--
|
||||||
[[rust-analyzer.highlightRelated.exitPoints.enable]]rust-analyzer.highlightRelated.exitPoints.enable (default: `true`)::
|
[[rust-analyzer.highlightRelated.exitPoints.enable]]rust-analyzer.highlightRelated.exitPoints.enable (default: `true`)::
|
||||||
+
|
+
|
||||||
--
|
--
|
||||||
|
@ -886,6 +886,11 @@
|
|||||||
"default": true,
|
"default": true,
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"rust-analyzer.highlightRelated.closureCaptures.enable": {
|
||||||
|
"markdownDescription": "Enables highlighting of all captures of a closure while the cursor is on the `|` or move keyword of a closure.",
|
||||||
|
"default": true,
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"rust-analyzer.highlightRelated.exitPoints.enable": {
|
"rust-analyzer.highlightRelated.exitPoints.enable": {
|
||||||
"markdownDescription": "Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).",
|
"markdownDescription": "Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).",
|
||||||
"default": true,
|
"default": true,
|
||||||
|
@ -10,12 +10,9 @@ export const hover = new lc.RequestType<
|
|||||||
HoverParams,
|
HoverParams,
|
||||||
(lc.Hover & { actions: CommandLinkGroup[] }) | null,
|
(lc.Hover & { actions: CommandLinkGroup[] }) | null,
|
||||||
void
|
void
|
||||||
>("textDocument/hover");
|
>(lc.HoverRequest.method);
|
||||||
export type HoverParams = { position: lc.Position | lc.Range } & Omit<
|
export type HoverParams = { position: lc.Position | lc.Range } & Omit<lc.HoverParams, "position">;
|
||||||
lc.TextDocumentPositionParams,
|
|
||||||
"position"
|
|
||||||
> &
|
|
||||||
lc.WorkDoneProgressParams;
|
|
||||||
export type CommandLink = {
|
export type CommandLink = {
|
||||||
/**
|
/**
|
||||||
* A tooltip for the command, when represented in the UI.
|
* A tooltip for the command, when represented in the UI.
|
||||||
|
Loading…
Reference in New Issue
Block a user