ink_analyzer/analysis/
actions.rs

1//! ink! attribute and entity code/intent actions.
2
3mod 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/// An ink! attribute code/intent action.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct Action {
20    /// Label which identifies the action.
21    pub label: String,
22    /// The kind of the action (e.g. quickfix or refactor).
23    pub kind: ActionKind,
24    /// Range where the action is activated.
25    pub range: TextRange,
26    /// Text edits that will be performed by the action.
27    pub edits: Vec<TextEdit>,
28}
29
30/// The kind of the action (e.g. quickfix or refactor).
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32#[non_exhaustive]
33pub enum ActionKind {
34    QuickFix,
35    Refactor,
36    Migrate,
37    Extract,
38}
39
40/// Computes ink! attribute actions for the text range.
41pub fn actions(file: &InkFile, range: TextRange, version: Version) -> Vec<Action> {
42    let mut results = Vec::new();
43
44    // Compute AST item-based ink! attribute actions.
45    item::actions(&mut results, file, range, version);
46
47    // Compute ink! attribute actions based on focused ink! attribute.
48    attr::actions(&mut results, file, range, version);
49
50    results
51        .into_iter()
52        // Deduplicate by edits.
53        .unique_by(|item| item.edits.clone())
54        // Format edits.
55        .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    /// Removes an ink! attribute.
64    pub(crate) fn remove_attribute(attr: &InkAttribute) -> Self {
65        Self::remove_attribute_with_label(attr, format!("Remove `{}` attribute.", attr.syntax()))
66    }
67
68    /// Removes an ink! attribute, and uses the given label for the action.
69    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    /// Removes an item.
79    pub(crate) fn remove_item(item: &SyntaxNode) -> Self {
80        Self::remove_item_with_label(item, "Remove item.".to_owned())
81    }
82
83    /// Removes an item, and uses the given label for the action.
84    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    /// Moves an item (i.e a syntax node) to a new location.
94    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    /// Moves an item (i.e a syntax node) to a new location with affixes (i.e. prefixes and suffixes).
104    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        // Gets the unindented insert text.
113        // NOTE: removes item's top-level indenting (if any).
114        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        // Applies indenting based on insert location (if specified).
121        if let Some(indent) = indent_option {
122            insert_text = utils::apply_indenting(insert_text.as_str(), indent);
123        }
124
125        // Adds prefix (if any).
126        if let Some(prefix) = prefix_option {
127            insert_text = format!("{prefix}{insert_text}");
128        }
129
130        // Adds suffix (if any).
131        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                // Insert a copy of the item at the specified offset.
141                TextEdit::insert(insert_text, offset),
142                // Delete the item from current location.
143                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}