1use strop_picker::{IndentChoice, Item, Kind, Payload, Picker};
7
8use super::document::IndentSource;
9use super::Editor;
10use crate::config::IndentStyle;
11
12impl Editor {
13 pub fn indentation_at(
15 &self,
16 document: strop_core::id::DocumentId,
17 byte: usize,
18 ) -> super::document::Indent {
19 let source = self.indent_target_at(document, byte).unwrap_or(document);
20 self.doc(source).indent
21 }
22
23 pub fn tab_width_for_path(&self, path: &std::path::Path) -> usize {
24 let target = crate::files::FileTarget::Local(self.cwd.join(path));
25 self.docs
26 .iter()
27 .find(|(_, document)| document.matches_target(&target))
28 .map_or(self.config.tab_size, |(_, document)| document.indent.width)
29 }
30
31 fn indent_target_at(
32 &self,
33 document: strop_core::id::DocumentId,
34 byte: usize,
35 ) -> Option<strop_core::id::DocumentId> {
36 if let Some((source, _)) = self.source_position(document, byte) {
37 return Some(source);
38 }
39 let view = self.docs.get(document)?;
40 let collection = self.collections.get(&document)?;
41 match collection.rows.get(view.buf.line_of(byte)) {
42 Some(super::collections::CollectionRow::CardTop(index)) => collection
43 .excerpts
44 .get(*index)
45 .map(|excerpt| excerpt.source),
46 _ => None,
47 }
48 }
49
50 fn indent_command_target(&mut self) -> Option<strop_core::id::DocumentId> {
51 let target = self.indent_target_at(self.current(), self.head());
52 if target.is_none() {
53 self.message = "place the caret in a source excerpt to change indentation".into();
54 }
55 target
56 }
57
58 pub(crate) fn tab_size_command(&mut self, arg: &str) {
62 let arg = arg.trim();
63 if arg.is_empty() {
64 self.open_tab_size_picker();
65 return;
66 }
67 let Some(document) = self.indent_command_target() else {
68 return;
69 };
70 if arg.eq_ignore_ascii_case("auto") {
71 self.set_width_override(document, None);
72 return;
73 }
74 match arg.parse::<usize>() {
75 Ok(width)
76 if (crate::config::TAB_SIZE_MIN..=crate::config::TAB_SIZE_MAX).contains(&width) =>
77 {
78 self.set_width_override(document, Some(width));
79 }
80 _ => {
81 self.message = format!(
82 "tab-size takes a width {}–{} or auto, not {arg:?}",
83 crate::config::TAB_SIZE_MIN,
84 crate::config::TAB_SIZE_MAX,
85 );
86 }
87 }
88 }
89
90 pub(crate) fn indent_style_command(&mut self, arg: &str) {
93 if arg.trim().is_empty() {
94 self.open_tab_size_picker();
95 return;
96 }
97 let Some(document) = self.indent_command_target() else {
98 return;
99 };
100 match arg.trim() {
101 "spaces" => self.set_style_override(document, Some(IndentStyle::Spaces)),
102 "tabs" => self.set_style_override(document, Some(IndentStyle::Tabs)),
103 "auto" => self.set_style_override(document, None),
104 other => {
105 self.message = format!("indent-style takes spaces, tabs or auto, not {other:?}")
106 }
107 }
108 }
109
110 pub fn indent_label(&self) -> String {
112 self.indentation_at(self.current(), self.head()).label()
113 }
114
115 pub(crate) fn open_tab_size_picker(&mut self) {
120 let Some(document) = self.indent_command_target() else {
121 return;
122 };
123 let indent = self.doc(document).indent;
124 let current = |on: bool, source: IndentSource| {
125 if on {
126 format!(" — current ({})", source.label())
127 } else {
128 String::new()
129 }
130 };
131 let mut items: Vec<Item> = [2usize, 3, 4, 8]
132 .into_iter()
133 .map(|width| Item {
134 badge: Some(width.to_string()),
135 text: format!(
136 "spaces per indent{}",
137 current(
138 indent.style == IndentStyle::Spaces && indent.width == width,
139 indent.width_source,
140 )
141 ),
142 payload: Payload::IndentChoice(IndentChoice::Width(width)),
143 })
144 .collect();
145 items.push(Item {
146 badge: None,
147 text: format!(
148 "auto width — detect/configure{}",
149 current(
150 indent.width_source != IndentSource::Manual,
151 indent.width_source
152 )
153 ),
154 payload: Payload::IndentChoice(IndentChoice::AutoWidth),
155 });
156 items.push(Item {
157 badge: None,
158 text: format!(
159 "style: spaces{}",
160 current(indent.style == IndentStyle::Spaces, indent.style_source)
161 ),
162 payload: Payload::IndentChoice(IndentChoice::Spaces),
163 });
164 items.push(Item {
165 badge: None,
166 text: format!(
167 "style: tabs{}",
168 current(indent.style == IndentStyle::Tabs, indent.style_source)
169 ),
170 payload: Payload::IndentChoice(IndentChoice::Tabs),
171 });
172 items.push(Item {
173 badge: None,
174 text: format!(
175 "style: auto — detect/configure{}",
176 current(
177 indent.style_source != IndentSource::Manual,
178 indent.style_source
179 )
180 ),
181 payload: Payload::IndentChoice(IndentChoice::AutoStyle),
182 });
183 items.push(Item {
186 badge: None,
187 text: format!(
188 "custom width… — type {}–{}, enter",
189 crate::config::TAB_SIZE_MIN,
190 crate::config::TAB_SIZE_MAX
191 ),
192 payload: Payload::IndentChoice(IndentChoice::CustomWidth),
193 });
194 let mut picker = Picker::new(Kind::TabSize, items, false);
195 picker.pinned_tail = 1;
196 let mut glue = super::picker::PickerGlue::diagnostics(picker);
197 glue.indent_target = Some(document);
198 self.set_picker(glue);
199 }
200
201 pub(crate) fn accept_indent_choice(
204 &mut self,
205 document: strop_core::id::DocumentId,
206 choice: IndentChoice,
207 draft: &str,
208 ) {
209 match choice {
210 IndentChoice::Width(width) => self.set_width_override(document, Some(width)),
211 IndentChoice::AutoWidth => self.set_width_override(document, None),
212 IndentChoice::Spaces => self.set_style_override(document, Some(IndentStyle::Spaces)),
213 IndentChoice::Tabs => self.set_style_override(document, Some(IndentStyle::Tabs)),
214 IndentChoice::AutoStyle => self.set_style_override(document, None),
215 IndentChoice::CustomWidth => match draft.parse::<usize>() {
216 Ok(width)
217 if (crate::config::TAB_SIZE_MIN..=crate::config::TAB_SIZE_MAX)
218 .contains(&width) =>
219 {
220 self.set_width_override(document, Some(width));
221 }
222 _ if draft.is_empty() => {
223 self.message = format!(
224 "type a width {}–{} first, then enter",
225 crate::config::TAB_SIZE_MIN,
226 crate::config::TAB_SIZE_MAX,
227 );
228 }
229 _ => {
230 self.message = format!(
231 "tab-size takes a width {}–{}, not {draft:?}",
232 crate::config::TAB_SIZE_MIN,
233 crate::config::TAB_SIZE_MAX,
234 );
235 }
236 },
237 }
238 }
239
240 fn set_width_override(&mut self, document: strop_core::id::DocumentId, width: Option<usize>) {
241 let Some(doc) = self.docs.get_mut(document) else {
242 self.message = "indentation source was closed".into();
243 return;
244 };
245 doc.indent_override.width = width;
246 self.resolve_indent_for(document);
247 self.message = self.indent_status(document);
248 }
249
250 fn set_style_override(
251 &mut self,
252 document: strop_core::id::DocumentId,
253 style: Option<IndentStyle>,
254 ) {
255 let Some(doc) = self.docs.get_mut(document) else {
256 self.message = "indentation source was closed".into();
257 return;
258 };
259 doc.indent_override.style = style;
260 self.resolve_indent_for(document);
261 self.message = self.indent_status(document);
262 }
263
264 fn indent_status(&self, document: strop_core::id::DocumentId) -> String {
267 let indent = self.doc(document).indent;
268 format!(
269 "{}: {} (width {}, style {})",
270 self.doc(document).label(&self.cwd),
271 indent.label(),
272 indent.width_source.label(),
273 indent.style_source.label(),
274 )
275 }
276}