ink_analyzer/analysis/
actions.rs1mod attr;
4pub mod entity;
5mod item;
6
7use std::cmp::Ordering;
8
9use ink_analyzer_ir::syntax::{SyntaxNode, TextRange, TextSize};
10use ink_analyzer_ir::{InkAttribute, InkFile};
11use itertools::Itertools;
12
13use super::utils;
14use crate::analysis::text_edit;
15use crate::{TextEdit, Version};
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct Action {
20 pub label: String,
22 pub kind: ActionKind,
24 pub range: TextRange,
26 pub edits: Vec<TextEdit>,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32#[non_exhaustive]
33pub enum ActionKind {
34 QuickFix,
35 Refactor,
36 Migrate,
37 Extract,
38}
39
40pub fn actions(file: &InkFile, range: TextRange, version: Version) -> Vec<Action> {
42 let mut results = Vec::new();
43
44 item::actions(&mut results, file, range, version);
46
47 attr::actions(&mut results, file, range, version);
49
50 results
51 .into_iter()
52 .unique_by(|item| item.edits.clone())
54 .map(|item| Action {
56 edits: text_edit::format_edits(item.edits.into_iter(), file).collect(),
57 ..item
58 })
59 .collect()
60}
61
62impl Action {
63 pub(crate) fn remove_attribute(attr: &InkAttribute) -> Self {
65 Self::remove_attribute_with_label(attr, format!("Remove `{}` attribute.", attr.syntax()))
66 }
67
68 pub(crate) fn remove_attribute_with_label(attr: &InkAttribute, label: String) -> Self {
70 Self {
71 label,
72 kind: ActionKind::QuickFix,
73 range: attr.syntax().text_range(),
74 edits: vec![TextEdit::delete(attr.syntax().text_range())],
75 }
76 }
77
78 pub(crate) fn remove_item(item: &SyntaxNode) -> Self {
80 Self::remove_item_with_label(item, "Remove item.".to_owned())
81 }
82
83 pub(crate) fn remove_item_with_label(item: &SyntaxNode, label: String) -> Self {
85 Self {
86 label,
87 kind: ActionKind::QuickFix,
88 range: item.text_range(),
89 edits: vec![TextEdit::delete(item.text_range())],
90 }
91 }
92
93 pub(crate) fn move_item(
95 item: &SyntaxNode,
96 offset: TextSize,
97 label: String,
98 indent_option: Option<&str>,
99 ) -> Self {
100 Self::move_item_with_affixes(item, offset, label, indent_option, None, None)
101 }
102
103 pub(crate) fn move_item_with_affixes(
105 item: &SyntaxNode,
106 offset: TextSize,
107 label: String,
108 indent_option: Option<&str>,
109 prefix_option: Option<&str>,
110 suffix_option: Option<&str>,
111 ) -> Self {
112 let mut insert_text = utils::item_indenting(item)
115 .map(|item_indent| {
116 utils::reduce_indenting(item.to_string().as_str(), item_indent.as_str())
117 })
118 .unwrap_or_else(|| item.to_string());
119
120 if let Some(indent) = indent_option {
122 insert_text = utils::apply_indenting(insert_text.as_str(), indent);
123 }
124
125 if let Some(prefix) = prefix_option {
127 insert_text = format!("{prefix}{insert_text}");
128 }
129
130 if let Some(suffix) = suffix_option {
132 insert_text = format!("{insert_text}{suffix}");
133 }
134
135 Self {
136 label,
137 kind: ActionKind::QuickFix,
138 range: item.text_range(),
139 edits: vec![
140 TextEdit::insert(insert_text, offset),
142 TextEdit::delete(item.text_range()),
144 ],
145 }
146 }
147}
148
149impl Ord for ActionKind {
150 fn cmp(&self, other: &Self) -> Ordering {
151 Ord::cmp(
152 &action_kind_sort_order(*self),
153 &action_kind_sort_order(*other),
154 )
155 }
156}
157
158impl PartialOrd for ActionKind {
159 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
160 Some(self.cmp(other))
161 }
162}
163
164fn action_kind_sort_order(kind: ActionKind) -> u8 {
165 match kind {
166 ActionKind::Migrate => 0,
167 ActionKind::QuickFix => 1,
168 ActionKind::Refactor => 2,
169 ActionKind::Extract => 3,
170 }
171}