@@ -3,21 +3,25 @@ pub(crate) mod dap;
3pub use dap::*;
3pub use dap::*;
5use helix_core::{
5use helix_core::{
6− comment, coords_at_pos, find_first_non_whitespace_char, find_root, graphemes, indent,
6+ comment, coords_at_pos, find_first_non_whitespace_char, find_root, graphemes,
7 indent::IndentStyle,
9 indent::IndentStyle,
8 line_ending::{get_line_ending_of_str, line_end_char_index, str_is_line_ending},
10 line_ending::{get_line_ending_of_str, line_end_char_index, str_is_line_ending},
9 match_brackets,
11 match_brackets,
10 movement::{self, Direction},
12 movement::{self, Direction},
13+ numbers::NumberIncrementor,
11 object, pos_at_coords,
14 object, pos_at_coords,
12 regex::{self, Regex, RegexBuilder},
15 regex::{self, Regex, RegexBuilder},
13 register::Register,
16 register::Register,
14− search, selection, surround, textobject, LineEnding, Position, Range, Rope, RopeGraphemes,
17+ search, selection, surround, textobject,
18+ unicode::width::UnicodeWidthChar,
15− RopeSlice, Selection, SmallVec, Tendril, Transaction,
19+ LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril,
18use helix_view::{
22use helix_view::{
19 clipboard::ClipboardType,
23 clipboard::ClipboardType,
20− document::Mode,
24+ document::{Mode, SCRATCH_BUFFER_NAME},
21 editor::{Action, Motion},
25 editor::{Action, Motion},
22 input::KeyEvent,
26 input::KeyEvent,
23 keyboard::KeyCode,
27 keyboard::KeyCode,
@@ -27,7 +31,7 @@ use helix_view::{
28use anyhow::{anyhow, bail, Context as _};
32use anyhow::{anyhow, bail, Context as _};
29use helix_lsp::{
33use helix_lsp::{
30− lsp,
34+ block_on, lsp,
31 util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
35 util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
32 OffsetEncoding,
36 OffsetEncoding,
@@ -189,6 +193,7 @@ impl Command {
189 copy_selection_on_prev_line, "Copy selection on previous line",
193 copy_selection_on_prev_line, "Copy selection on previous line",
190 move_next_word_start, "Move to beginning of next word",
194 move_next_word_start, "Move to beginning of next word",
191 move_prev_word_start, "Move to beginning of previous word",
195 move_prev_word_start, "Move to beginning of previous word",
196+ move_prev_word_end, "Move to end of previous word",
192 move_next_word_end, "Move to end of next word",
197 move_next_word_end, "Move to end of next word",
193 move_next_long_word_start, "Move to beginning of next long word",
198 move_next_long_word_start, "Move to beginning of next long word",
194 move_prev_long_word_start, "Move to beginning of previous long word",
199 move_prev_long_word_start, "Move to beginning of previous long word",
@@ -241,6 +246,7 @@ impl Command {
241 code_action, "Perform code action",
246 code_action, "Perform code action",
242 buffer_picker, "Open buffer picker",
247 buffer_picker, "Open buffer picker",
243 symbol_picker, "Open symbol picker",
248 symbol_picker, "Open symbol picker",
249+ workspace_symbol_picker, "Open workspace symbol picker",
244 last_picker, "Open last picker",
250 last_picker, "Open last picker",
245 prepend_to_line, "Insert at start of line",
251 prepend_to_line, "Insert at start of line",
246 append_to_line, "Insert at end of line",
252 append_to_line, "Insert at end of line",
@@ -261,6 +267,7 @@ impl Command {
261 goto_window_middle, "Goto window middle",
267 goto_window_middle, "Goto window middle",
262 goto_window_bottom, "Goto window bottom",
268 goto_window_bottom, "Goto window bottom",
263 goto_last_accessed_file, "Goto last accessed file",
269 goto_last_accessed_file, "Goto last accessed file",
270+ goto_last_modification, "Goto last modification",
264 goto_line, "Goto line",
271 goto_line, "Goto line",
265 goto_last_line, "Goto last line",
272 goto_last_line, "Goto last line",
266 goto_first_diag, "Goto first diagnostic",
273 goto_first_diag, "Goto first diagnostic",
@@ -274,6 +281,7 @@ impl Command {
274 // TODO: different description ?
281 // TODO: different description ?
275 goto_line_end_newline, "Goto line end",
282 goto_line_end_newline, "Goto line end",
276 goto_first_nonwhitespace, "Goto first non-blank in line",
283 goto_first_nonwhitespace, "Goto first non-blank in line",
284+ trim_selections, "Trim whitespace from selections",
277 extend_to_line_start, "Extend to line start",
285 extend_to_line_start, "Extend to line start",
278 extend_to_line_end, "Extend to line end",
286 extend_to_line_end, "Extend to line end",
279 extend_to_line_end_newline, "Extend to line end",
287 extend_to_line_end_newline, "Extend to line end",
@@ -283,8 +291,13 @@ impl Command {
283 delete_char_backward, "Delete previous char",
291 delete_char_backward, "Delete previous char",
284 delete_char_forward, "Delete next char",
292 delete_char_forward, "Delete next char",
285 delete_word_backward, "Delete previous word",
293 delete_word_backward, "Delete previous word",
294+ delete_word_forward, "Delete next word",
295+ kill_to_line_start, "Delete content till the start of the line",
296+ kill_to_line_end, "Delete content till the end of the line",
286 undo, "Undo change",
297 undo, "Undo change",
287 redo, "Redo change",
298 redo, "Redo change",
299+ earlier, "Move backward in history",
300+ later, "Move forward in history",
288 yank, "Yank selection",
301 yank, "Yank selection",
289 yank_joined_to_clipboard, "Join and yank selections to clipboard",
302 yank_joined_to_clipboard, "Join and yank selections to clipboard",
290 yank_main_selection_to_clipboard, "Yank main selection to clipboard",
303 yank_main_selection_to_clipboard, "Yank main selection to clipboard",
@@ -304,6 +317,7 @@ impl Command {
304 format_selections, "Format selection",
317 format_selections, "Format selection",
305 join_selections, "Join lines inside selection",
318 join_selections, "Join lines inside selection",
306 keep_selections, "Keep selections matching regex",
319 keep_selections, "Keep selections matching regex",
320+ remove_selections, "Remove selections matching regex",
307 keep_primary_selection, "Keep primary selection",
321 keep_primary_selection, "Keep primary selection",
308 remove_primary_selection, "Remove primary selection",
322 remove_primary_selection, "Remove primary selection",
309 completion, "Invoke completion popup",
323 completion, "Invoke completion popup",
@@ -324,7 +338,9 @@ impl Command {
324 hsplit, "Horizontal bottom split",
338 hsplit, "Horizontal bottom split",
325 vsplit, "Vertical right split",
339 vsplit, "Vertical right split",
326 wclose, "Close window",
340 wclose, "Close window",
341+ wonly, "Current window only",
327 select_register, "Select register",
342 select_register, "Select register",
343+ insert_register, "Insert register",
328 align_view_middle, "Align view middle",
344 align_view_middle, "Align view middle",
329 align_view_top, "Align view top",
345 align_view_top, "Align view top",
330 align_view_center, "Align view center",
346 align_view_center, "Align view center",
@@ -358,6 +374,9 @@ impl Command {
358 shell_append_output, "Append output of shell command after each selection",
374 shell_append_output, "Append output of shell command after each selection",
359 shell_keep_pipe, "Filter selections with shell predicate",
375 shell_keep_pipe, "Filter selections with shell predicate",
360 suspend, "Suspend",
376 suspend, "Suspend",
377+ rename_symbol, "Rename symbol",
378+ increment, "Increment",
379+ decrement, "Decrement",
@@ -581,6 +600,29 @@ fn extend_to_line_start(cx: &mut Context) {
581 goto_line_start_impl(view, doc, Movement::Extend)
600 goto_line_start_impl(view, doc, Movement::Extend)
603+fn kill_to_line_start(cx: &mut Context) {
604+ let (view, doc) = current!(cx.editor);
605+ let text = doc.text().slice(..);
607+ let selection = doc.selection(view.id).clone().transform(|range| {
608+ let line = range.cursor_line(text);
609+ range.put_cursor(text, text.line_to_char(line), true)
611+ delete_selection_insert_mode(doc, view, &selection);
614+fn kill_to_line_end(cx: &mut Context) {
615+ let (view, doc) = current!(cx.editor);
616+ let text = doc.text().slice(..);
618+ let selection = doc.selection(view.id).clone().transform(|range| {
619+ let line = range.cursor_line(text);
620+ let pos = line_end_char_index(&text, line);
621+ range.put_cursor(text, pos, true)
623+ delete_selection_insert_mode(doc, view, &selection);
584fn goto_first_nonwhitespace(cx: &mut Context) {
626fn goto_first_nonwhitespace(cx: &mut Context) {
585 let (view, doc) = current!(cx.editor);
627 let (view, doc) = current!(cx.editor);
586 let text = doc.text().slice(..);
628 let text = doc.text().slice(..);
@@ -598,6 +640,42 @@ fn goto_first_nonwhitespace(cx: &mut Context) {
598 doc.set_selection(view.id, selection);
640 doc.set_selection(view.id, selection);
643+fn trim_selections(cx: &mut Context) {
644+ let (view, doc) = current!(cx.editor);
645+ let text = doc.text().slice(..);
647+ let ranges: SmallVec<[Range; 1]> = doc
650+ .filter_map(|range| {
651+ if range.is_empty() || range.fragment(text).chars().all(|ch| ch.is_whitespace()) {
654+ let mut start = range.from();
655+ let mut end = range.to();
656+ start = movement::skip_while(text, start, |x| x.is_whitespace()).unwrap_or(start);
657+ end = movement::backwards_skip_while(text, end, |x| x.is_whitespace()).unwrap_or(end);
658+ if range.anchor < range.head {
659+ Some(Range::new(start, end))
661+ Some(Range::new(end, start))
666+ if !ranges.is_empty() {
667+ let primary = doc.selection(view.id).primary();
670+ .position(|range| range.overlaps(&primary))
671+ .unwrap_or(ranges.len() - 1);
672+ doc.set_selection(view.id, Selection::new(ranges, idx));
674+ collapse_selection(cx);
675+ keep_primary_selection(cx);
601fn goto_window(cx: &mut Context, align: Align) {
679fn goto_window(cx: &mut Context, align: Align) {
602 let (view, doc) = current!(cx.editor);
680 let (view, doc) = current!(cx.editor);
@@ -657,6 +735,10 @@ fn move_prev_word_start(cx: &mut Context) {
657 move_word_impl(cx, movement::move_prev_word_start)
735 move_word_impl(cx, movement::move_prev_word_start)
738+fn move_prev_word_end(cx: &mut Context) {
739+ move_word_impl(cx, movement::move_prev_word_end)
660fn move_next_word_end(cx: &mut Context) {
742fn move_next_word_end(cx: &mut Context) {
661 move_word_impl(cx, movement::move_next_word_end)
743 move_word_impl(cx, movement::move_next_word_end)
@@ -1193,6 +1275,7 @@ fn search_impl(
1193 regex: &Regex,
1275 regex: &Regex,
1194 movement: Movement,
1276 movement: Movement,
1195 direction: Direction,
1277 direction: Direction,
1197 let text = doc.text().slice(..);
1280 let text = doc.text().slice(..);
1198 let selection = doc.selection(view.id);
1281 let selection = doc.selection(view.id);
@@ -1251,7 +1334,11 @@ fn search_impl(
1253 doc.set_selection(view.id, selection);
1336 doc.set_selection(view.id, selection);
1337+ if view.is_cursor_in_view(doc, 0) {
1338+ view.ensure_cursor_in_view(doc, scrolloff);
1254− align_view(doc, view, Align::Center);
1340+ align_view(doc, view, Align::Center)
@@ -1275,6 +1362,8 @@ fn rsearch(cx: &mut Context) {
1275// TODO: use one function for search vs extend
1362// TODO: use one function for search vs extend
1276fn searcher(cx: &mut Context, direction: Direction) {
1363fn searcher(cx: &mut Context, direction: Direction) {
1277 let reg = cx.register.unwrap_or('/');
1364 let reg = cx.register.unwrap_or('/');
1365+ let scrolloff = cx.editor.config.scrolloff;
1278 let (_, doc) = current!(cx.editor);
1367 let (_, doc) = current!(cx.editor);
1280 // TODO: could probably share with select_on_matches?
1369 // TODO: could probably share with select_on_matches?
@@ -1299,7 +1388,15 @@ fn searcher(cx: &mut Context, direction: Direction) {
1299 if event != PromptEvent::Update {
1388 if event != PromptEvent::Update {
1302− search_impl(doc, view, &contents, ®ex, Movement::Move, direction);
1391+ search_impl(
@@ -1307,6 +1404,7 @@ fn searcher(cx: &mut Context, direction: Direction) {
1309fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) {
1406fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) {
1407+ let scrolloff = cx.editor.config.scrolloff;
1310 let (view, doc) = current!(cx.editor);
1408 let (view, doc) = current!(cx.editor);
1311 let registers = &cx.editor.registers;
1409 let registers = &cx.editor.registers;
1312 if let Some(query) = registers.read('/') {
1410 if let Some(query) = registers.read('/') {
@@ -1321,7 +1419,7 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
1321 .case_insensitive(case_insensitive)
1419 .case_insensitive(case_insensitive)
1322 .build()
1420 .build()
1324− search_impl(doc, view, &contents, ®ex, movement, direction);
1422+ search_impl(doc, view, &contents, ®ex, movement, direction, scrolloff);
1325 } else {
1423 } else {
1326 // get around warning `mutable_borrow_reservation_conflict`
1424 // get around warning `mutable_borrow_reservation_conflict`
1327 // which will be a hard error in the future
1425 // which will be a hard error in the future
@@ -1365,7 +1463,7 @@ fn global_search(cx: &mut Context) {
1365 let completions = search_completions(cx, None);
1463 let completions = search_completions(cx, None);
1366 let prompt = ui::regex_prompt(
1464 let prompt = ui::regex_prompt(
1368− "global search:".into(),
1466+ "global-search:".into(),
1370 move |input: &str| {
1468 move |input: &str| {
1371 completions
1469 completions
@@ -1543,6 +1641,17 @@ fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId
1543 doc.apply(&transaction, view_id);
1641 doc.apply(&transaction, view_id);
1645+fn delete_selection_insert_mode(doc: &mut Document, view: &View, selection: &Selection) {
1646+ let view_id = view.id;
1649+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
1650+ (range.from(), range.to(), None)
1652+ doc.apply(&transaction, view_id);
1546fn delete_selection(cx: &mut Context) {
1655fn delete_selection(cx: &mut Context) {
1547 let reg_name = cx.register.unwrap_or('"');
1656 let reg_name = cx.register.unwrap_or('"');
1548 let (view, doc) = current!(cx.editor);
1657 let (view, doc) = current!(cx.editor);
@@ -1657,8 +1766,7 @@ mod cmd {
1657 buffers_remaining_impl(cx.editor)?
1766 buffers_remaining_impl(cx.editor)?
1661− .close(view!(cx.editor).id, /* close_buffer */ false);
1769+ cx.editor.close(view!(cx.editor).id);
@@ -1668,8 +1776,7 @@ mod cmd {
1668 _args: &[&str],
1776 _args: &[&str],
1669 _event: PromptEvent,
1777 _event: PromptEvent,
1670 ) -> anyhow::Result<()> {
1778 ) -> anyhow::Result<()> {
1672− .close(view!(cx.editor).id, /* close_buffer */ false);
1779+ cx.editor.close(view!(cx.editor).id);
@@ -1679,11 +1786,30 @@ mod cmd {
1679 args: &[&str],
1786 args: &[&str],
1680 _event: PromptEvent,
1787 _event: PromptEvent,
1681 ) -> anyhow::Result<()> {
1788 ) -> anyhow::Result<()> {
1682− use helix_core::path::expand_tilde;
1683 let path = args.get(0).context("wrong argument count")?;
1789 let path = args.get(0).context("wrong argument count")?;
1686− .open(expand_tilde(Path::new(path)), Action::Replace)?;
1790+ let _ = cx.editor.open(path.into(), Action::Replace)?;
1795+ cx: &mut compositor::Context,
1797+ _event: PromptEvent,
1798+ ) -> anyhow::Result<()> {
1799+ let view = view!(cx.editor);
1800+ let doc_id = view.doc;
1801+ cx.editor.close_document(doc_id, false)?;
1805+ fn force_buffer_close(
1806+ cx: &mut compositor::Context,
1808+ _event: PromptEvent,
1809+ ) -> anyhow::Result<()> {
1810+ let view = view!(cx.editor);
1811+ let doc_id = view.doc;
1812+ cx.editor.close_document(doc_id, true)?;
@@ -1838,13 +1964,13 @@ mod cmd {
1838 args: &[&str],
1964 args: &[&str],
1839 _event: PromptEvent,
1965 _event: PromptEvent,
1840 ) -> anyhow::Result<()> {
1966 ) -> anyhow::Result<()> {
1843− .parse::<helix_core::history::UndoKind>()
1844− .map_err(|s| anyhow!(s))?;
1967+ let uk = args.join(" ").parse::<UndoKind>().map_err(|s| anyhow!(s))?;
1846 let (view, doc) = current!(cx.editor);
1969 let (view, doc) = current!(cx.editor);
1847− doc.earlier(view.id, uk);
1970+ let success = doc.earlier(view.id, uk);
1972+ cx.editor.set_status("Already at oldest change".to_owned());
@@ -1854,12 +1980,12 @@ mod cmd {
1854 args: &[&str],
1980 args: &[&str],
1855 _event: PromptEvent,
1981 _event: PromptEvent,
1856 ) -> anyhow::Result<()> {
1982 ) -> anyhow::Result<()> {
1859− .parse::<helix_core::history::UndoKind>()
1860− .map_err(|s| anyhow!(s))?;
1983+ let uk = args.join(" ").parse::<UndoKind>().map_err(|s| anyhow!(s))?;
1861 let (view, doc) = current!(cx.editor);
1984 let (view, doc) = current!(cx.editor);
1862− doc.later(view.id, uk);
1985+ let success = doc.later(view.id, uk);
1987+ cx.editor.set_status("Already at newest change".to_owned());
@@ -1891,7 +2017,7 @@ mod cmd {
1891 .map(|doc| {
2017 .map(|doc| {
1892 doc.relative_path()
2018 doc.relative_path()
1893 .map(|path| path.to_string_lossy().to_string())
2019 .map(|path| path.to_string_lossy().to_string())
1894− .unwrap_or_else(|| "[scratch]".into())
2020+ .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into())
1896 .collect();
2022 .collect();
1897 if !modified.is_empty() {
2023 if !modified.is_empty() {
@@ -1933,7 +2059,7 @@ mod cmd {
1933 // close all views
2059 // close all views
1934 let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect();
2060 let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect();
1935 for view_id in views {
2061 for view_id in views {
1936− cx.editor.close(view_id, false);
2062+ cx.editor.close(view_id);
@@ -1977,7 +2103,7 @@ mod cmd {
1977 // close all views
2103 // close all views
1978 let views: Vec<_> = editor.tree.views().map(|(view, _)| view.id).collect();
2104 let views: Vec<_> = editor.tree.views().map(|(view, _)| view.id).collect();
1979 for view_id in views {
2105 for view_id in views {
1980− editor.close(view_id, false);
2106+ editor.close(view_id);
@@ -1999,6 +2125,25 @@ mod cmd {
1999 quit_all_impl(&mut cx.editor, args, event, true)
2125 quit_all_impl(&mut cx.editor, args, event, true)
2129+ cx: &mut compositor::Context,
2131+ _event: PromptEvent,
2132+ ) -> anyhow::Result<()> {
2133+ let exit_code = args
2135+ .and_then(|code| code.parse::<i32>().ok())
2137+ cx.editor.exit_code = exit_code;
2139+ let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect();
2140+ for view_id in views {
2141+ cx.editor.close(view_id);
2002 fn theme(
2147 fn theme(
2003 cx: &mut compositor::Context,
2148 cx: &mut compositor::Context,
2004 args: &[&str],
2149 args: &[&str],
@@ -2241,7 +2386,6 @@ mod cmd {
2241 args: &[&str],
2386 args: &[&str],
2242 _event: PromptEvent,
2387 _event: PromptEvent,
2243 ) -> anyhow::Result<()> {
2388 ) -> anyhow::Result<()> {
2244− use helix_lsp::block_on;
2245 if let Some(debugger) = cx.editor.debugger.as_mut() {
2389 if let Some(debugger) = cx.editor.debugger.as_mut() {
2246 let (frame, thread_id) = match (debugger.active_frame, debugger.thread_id) {
2390 let (frame, thread_id) = match (debugger.active_frame, debugger.thread_id) {
2247 (Some(frame), Some(thread_id)) => (frame, thread_id),
2391 (Some(frame), Some(thread_id)) => (frame, thread_id),
@@ -2344,6 +2488,20 @@ mod cmd {
2344 fun: open,
2488 fun: open,
2345 completer: Some(completers::filename),
2489 completer: Some(completers::filename),
2492+ name: "buffer-close",
2493+ aliases: &["bc", "bclose"],
2494+ doc: "Close the current buffer.",
2496+ completer: None, // FIXME: buffer completer
2499+ name: "buffer-close!",
2500+ aliases: &["bc!", "bclose!"],
2501+ doc: "Close the current buffer forcefully (ignoring unsaved changes).",
2502+ fun: force_buffer_close,
2503+ completer: None, // FIXME: buffer completer
2347 TypableCommand {
2505 TypableCommand {
2348 name: "write",
2506 name: "write",
2349 aliases: &["w"],
2507 aliases: &["w"],
@@ -2442,6 +2600,13 @@ mod cmd {
2442 fun: force_quit_all,
2600 fun: force_quit_all,
2443 completer: None,
2601 completer: None,
2606+ doc: "Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2).",
2445 TypableCommand {
2610 TypableCommand {
2446 name: "theme",
2611 name: "theme",
2447 aliases: &[],
2612 aliases: &[],
@@ -2698,36 +2863,66 @@ fn file_picker(cx: &mut Context) {
2698fn buffer_picker(cx: &mut Context) {
2863fn buffer_picker(cx: &mut Context) {
2699 let current = view!(cx.editor).doc;
2864 let current = view!(cx.editor).doc;
2866+ struct BufferMeta {
2868+ path: Option<PathBuf>,
2874+ fn format(&self) -> Cow<str> {
2878+ .map(helix_core::path::get_relative_path);
2879+ let path = match path.as_deref().and_then(Path::to_str) {
2880+ Some(path) => path,
2881+ None => return Cow::Borrowed(SCRATCH_BUFFER_NAME),
2884+ let mut flags = Vec::new();
2885+ if self.is_modified {
2888+ if self.is_current {
2892+ let flag = if flags.is_empty() {
2895+ format!(" ({})", flags.join(""))
2897+ Cow::Owned(format!("{}{}", path, flag))
2901+ let new_meta = |doc: &Document| BufferMeta {
2903+ path: doc.path().cloned(),
2904+ is_modified: doc.is_modified(),
2905+ is_current: doc.id() == current,
2701 let picker = FilePicker::new(
2908 let picker = FilePicker::new(
2702 cx.editor
2909 cx.editor
2703 .documents
2910 .documents
2705− .map(|(id, doc)| (*id, doc.path().cloned()))
2912+ .map(|(_, doc)| new_meta(doc))
2706 .collect(),
2913 .collect(),
2707− move |(id, path): &(DocumentId, Option<PathBuf>)| {
2708− let path = path.as_deref().map(helix_core::path::get_relative_path);
2709− match path.as_ref().and_then(|path| path.to_str()) {
2711− if *id == current {
2712− format!("{} (*)", &path).into()
2914+ BufferMeta::format,
2713− } else {
2915+ |cx, meta, _action| {
2714− path.to_owned().into()
2717− None => "[scratch buffer]".into(),
2720− |cx, (id, _path): &(DocumentId, Option<PathBuf>), _action| {
2721− cx.editor.switch(*id, Action::Replace);
2916+ cx.editor.switch(meta.id, Action::Replace);
2723− |editor, (id, path)| {
2918+ |editor, meta| {
2724− let doc = &editor.documents.get(id)?;
2919+ let doc = &editor.documents.get(&meta.id)?;
2725 let &view_id = doc.selections().keys().next()?;
2920 let &view_id = doc.selections().keys().next()?;
2726 let line = doc
2921 let line = doc
2727 .selection(view_id)
2922 .selection(view_id)
2728 .primary()
2923 .primary()
2729 .cursor_line(doc.text().slice(..));
2924 .cursor_line(doc.text().slice(..));
2730− Some((path.clone()?, Some((line, line))))
2925+ Some((meta.path.clone()?, Some((line, line))))
2733 cx.push_layer(Box::new(picker));
2928 cx.push_layer(Box::new(picker));
@@ -2782,7 +2977,7 @@ fn symbol_picker(cx: &mut Context) {
2785− let picker = FilePicker::new(
2980+ let mut picker = FilePicker::new(
2786 symbols,
2981 symbols,
2787 |symbol| (&symbol.name).into(),
2982 |symbol| (&symbol.name).into(),
2788 move |cx, symbol, _action| {
2983 move |cx, symbol, _action| {
@@ -2807,6 +3002,69 @@ fn symbol_picker(cx: &mut Context) {
2807 Some((path, line))
3002 Some((path, line))
3005+ picker.truncate_start = false;
3006+ compositor.push(Box::new(picker))
3012+fn workspace_symbol_picker(cx: &mut Context) {
3013+ let (_, doc) = current!(cx.editor);
3015+ let language_server = match doc.language_server() {
3016+ Some(language_server) => language_server,
3019+ let offset_encoding = language_server.offset_encoding();
3021+ let future = language_server.workspace_symbols("".to_string());
3023+ let current_path = doc_mut!(cx.editor).path().cloned();
3026+ move |_editor: &mut Editor,
3027+ compositor: &mut Compositor,
3028+ response: Option<Vec<lsp::SymbolInformation>>| {
3029+ if let Some(symbols) = response {
3030+ let mut picker = FilePicker::new(
3033+ let path = symbol.location.uri.to_file_path().unwrap();
3034+ if current_path.as_ref().map(|p| p == &path).unwrap_or(false) {
3035+ (&symbol.name).into()
3037+ let relative_path = helix_core::path::get_relative_path(path.as_path())
3041+ format!("{} ({})", &symbol.name, relative_path).into()
3044+ move |cx, symbol, action| {
3045+ let path = symbol.location.uri.to_file_path().unwrap();
3046+ cx.editor.open(path, action).expect("editor.open failed");
3047+ let (view, doc) = current!(cx.editor);
3049+ if let Some(range) =
3050+ lsp_range_to_range(doc.text(), symbol.location.range, offset_encoding)
3052+ // we flip the range so that the cursor sits on the start of the symbol
3053+ // (for example start of the function).
3054+ doc.set_selection(view.id, Selection::single(range.head, range.anchor));
3055+ align_view(doc, view, Align::Center);
3058+ move |_editor, symbol| {
3059+ let path = symbol.location.uri.to_file_path().unwrap();
3061+ symbol.location.range.start.line as usize,
3062+ symbol.location.range.end.line as usize,
3067+ picker.truncate_start = false;
2810 compositor.push(Box::new(picker))
3068 compositor.push(Box::new(picker))
@@ -2864,14 +3122,104 @@ pub fn code_action(cx: &mut Context) {
3125+pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
3126+ use lsp::ResourceOp;
3129+ ResourceOp::Create(op) => {
3130+ let path = op.uri.to_file_path().unwrap();
3131+ let ignore_if_exists = if let Some(options) = &op.options {
3132+ !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
3136+ if ignore_if_exists && path.exists() {
3139+ fs::write(&path, [])
3142+ ResourceOp::Delete(op) => {
3143+ let path = op.uri.to_file_path().unwrap();
3145+ let recursive = if let Some(options) = &op.options {
3146+ options.recursive.unwrap_or(false)
3151+ fs::remove_dir_all(&path)
3153+ fs::remove_dir(&path)
3155+ } else if path.is_file() {
3156+ fs::remove_file(&path)
3161+ ResourceOp::Rename(op) => {
3162+ let from = op.old_uri.to_file_path().unwrap();
3163+ let to = op.new_uri.to_file_path().unwrap();
3164+ let ignore_if_exists = if let Some(options) = &op.options {
3165+ !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
3169+ if ignore_if_exists && to.exists() {
3172+ fs::rename(&from, &to)
2867fn apply_workspace_edit(
3178fn apply_workspace_edit(
2868 editor: &mut Editor,
3179 editor: &mut Editor,
2869 offset_encoding: OffsetEncoding,
3180 offset_encoding: OffsetEncoding,
2870 workspace_edit: &lsp::WorkspaceEdit,
3181 workspace_edit: &lsp::WorkspaceEdit,
3183+ let mut apply_edits = |uri: &helix_lsp::Url, text_edits: Vec<lsp::TextEdit>| {
3186+ .expect("unable to convert URI to filepath");
3188+ let current_view_id = view!(editor).id;
3189+ let doc_id = editor.open(path, Action::Load).unwrap();
3191+ .document_mut(doc_id)
3192+ .expect("Document for document_changes not found");
3194+ // Need to determine a view for apply/append_changes_to_history
3195+ let selections = doc.selections();
3196+ let view_id = if selections.contains_key(¤t_view_id) {
3197+ // use current if possible
3200+ // Hack: we take the first available view_id
3205+ .expect("No view_id available")
3208+ let transaction = helix_lsp::util::generate_transaction_from_edits(
3213+ doc.apply(&transaction, view_id);
3214+ doc.append_changes_to_history(view_id);
2872 if let Some(ref changes) = workspace_edit.changes {
3217 if let Some(ref changes) = workspace_edit.changes {
2873 log::debug!("workspace changes: {:?}", changes);
3218 log::debug!("workspace changes: {:?}", changes);
2874− editor.set_error(String::from("Handling workspace_edit.changes is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
3219+ for (uri, text_edits) in changes {
3220+ let text_edits = text_edits.to_vec();
3221+ apply_edits(uri, text_edits);
2876 // Not sure if it works properly, it'll be safer to just panic here to avoid breaking some parts of code on which code actions will be used
3224 // Not sure if it works properly, it'll be safer to just panic here to avoid breaking some parts of code on which code actions will be used
2877 // TODO: find some example that uses workspace changes, and test it
3225 // TODO: find some example that uses workspace changes, and test it
@@ -2889,30 +3237,6 @@ fn apply_workspace_edit(
2889 match document_changes {
3237 match document_changes {
2890 lsp::DocumentChanges::Edits(document_edits) => {
3238 lsp::DocumentChanges::Edits(document_edits) => {
2891 for document_edit in document_edits {
3239 for document_edit in document_edits {
2892− let path = document_edit
2896− .expect("unable to convert URI to filepath");
2897− let current_view_id = view!(editor).id;
2899− .document_by_path_mut(path)
2900− .expect("Document for document_changes not found");
2902− // Need to determine a view for apply/append_changes_to_history
2903− let selections = doc.selections();
2904− let view_id = if selections.contains_key(¤t_view_id) {
2905− // use current if possible
2908− // Hack: we take the first available view_id
2913− .expect("No view_id available")
2916 let edits = document_edit
3240 let edits = document_edit
@@ -2924,19 +3248,33 @@ fn apply_workspace_edit(
2925 .cloned()
3249 .cloned()
2926 .collect();
3250 .collect();
2927−
3251+ apply_edits(&document_edit.text_document.uri, edits);
2928− let transaction = helix_lsp::util::generate_transaction_from_edits(
2933− doc.apply(&transaction, view_id);
2934− doc.append_changes_to_history(view_id);
2937 lsp::DocumentChanges::Operations(operations) => {
3254 lsp::DocumentChanges::Operations(operations) => {
2938 log::debug!("document changes - operations: {:?}", operations);
3255 log::debug!("document changes - operations: {:?}", operations);
2939− editor.set_error(String::from("Handling document operations is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
3256+ for operateion in operations {
3258+ lsp::DocumentChangeOperation::Op(op) => {
3259+ apply_document_resource_op(op).unwrap();
3262+ lsp::DocumentChangeOperation::Edit(document_edit) => {
3263+ let edits = document_edit
3266+ .map(|edit| match edit {
3267+ lsp::OneOf::Left(text_edit) => text_edit,
3268+ lsp::OneOf::Right(annotated_text_edit) => {
3269+ &annotated_text_edit.text_edit
3274+ apply_edits(&document_edit.text_document.uri, edits);
@@ -3175,6 +3513,19 @@ fn goto_last_accessed_file(cx: &mut Context) {
3516+fn goto_last_modification(cx: &mut Context) {
3517+ let (view, doc) = current!(cx.editor);
3518+ let pos = doc.history.get_mut().last_edit_pos();
3519+ let text = doc.text().slice(..);
3520+ if let Some(pos) = pos {
3521+ let selection = doc
3522+ .selection(view.id)
3524+ .transform(|range| range.put_cursor(text, pos, doc.mode == Mode::Select));
3525+ doc.set_selection(view.id, selection);
3178fn select_mode(cx: &mut Context) {
3529fn select_mode(cx: &mut Context) {
3179 let (view, doc) = current!(cx.editor);
3530 let (view, doc) = current!(cx.editor);
3180 let text = doc.text().slice(..);
3531 let text = doc.text().slice(..);
@@ -3773,19 +4124,70 @@ pub mod insert {
3773 doc.apply(&transaction, view.id);
4124 doc.apply(&transaction, view.id);
3776− // TODO: handle indent-aware delete
3777 pub fn delete_char_backward(cx: &mut Context) {
4127 pub fn delete_char_backward(cx: &mut Context) {
3778 let count = cx.count();
4128 let count = cx.count();
3779 let (view, doc) = current!(cx.editor);
4129 let (view, doc) = current!(cx.editor);
3780 let text = doc.text().slice(..);
4130 let text = doc.text().slice(..);
4131+ let indent_unit = doc.indent_unit();
4132+ let tab_size = doc.tab_width();
3781 let transaction =
4134 let transaction =
3782 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
4135 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3783 let pos = range.cursor(text);
4136 let pos = range.cursor(text);
3784− (
4137+ let line_start_pos = text.line_to_char(range.cursor_line(text));
4138+ // considier to delete by indent level if all characters before `pos` are indent units.
4139+ let fragment = Cow::from(text.slice(line_start_pos..pos));
4140+ if !fragment.is_empty() && fragment.chars().all(|ch| ch.is_whitespace()) {
4141+ if text.get_char(pos.saturating_sub(1)) == Some('\t') {
4142+ // fast path, delete one char
3785− graphemes::nth_prev_grapheme_boundary(text, pos, count),
4144+ graphemes::nth_prev_grapheme_boundary(text, pos, 1),
4149+ let unit_len = indent_unit.chars().count();
4150+ // NOTE: indent_unit always contains 'only spaces' or 'only tab' according to `IndentStyle` definition.
4151+ let unit_size = if indent_unit.starts_with('\t') {
4152+ tab_size * unit_len
4156+ let width: usize = fragment
4162+ // it can be none if it still meet control characters other than '\t'
4163+ // here just set the width to 1 (or some value better?).
4164+ ch.width().unwrap_or(1)
4168+ let mut drop = width % unit_size; // round down to nearest unit
4171+ }; // if it's already at a unit, consume a whole unit
4172+ let mut chars = fragment.chars().rev();
4173+ let mut start = pos;
4175+ // delete up to `drop` spaces
4176+ match chars.next() {
4177+ Some(' ') => start -= 1,
4181+ (start, pos, None) // delete!
4186+ graphemes::nth_prev_grapheme_boundary(text, pos, count),
3790 doc.apply(&transaction, view.id);
4192 doc.apply(&transaction, view.id);
@@ -3815,8 +4217,19 @@ pub mod insert {
3815 .selection(view.id)
4217 .selection(view.id)
3816 .clone()
4218 .clone()
3817 .transform(|range| movement::move_prev_word_start(text, range, count));
4219 .transform(|range| movement::move_prev_word_start(text, range, count));
3818− doc.set_selection(view.id, selection);
4220+ delete_selection_insert_mode(doc, view, &selection);
3819− delete_selection(cx)
4221+ }
4223+ pub fn delete_word_forward(cx: &mut Context) {
4224+ let count = cx.count();
4225+ let (view, doc) = current!(cx.editor);
4226+ let text = doc.text().slice(..);
4228+ let selection = doc
4229+ .selection(view.id)
4231+ .transform(|range| movement::move_next_word_start(text, range, count));
4232+ delete_selection_insert_mode(doc, view, &selection);
@@ -3826,20 +4239,48 @@ pub mod insert {
3826// storing it?
4239// storing it?
3828fn undo(cx: &mut Context) {
4241fn undo(cx: &mut Context) {
4242+ let count = cx.count();
3829 let (view, doc) = current!(cx.editor);
4243 let (view, doc) = current!(cx.editor);
3830− let view_id = view.id;
4244+ for _ in 0..count {
3831− let success = doc.undo(view_id);
4245+ if !doc.undo(view.id) {
3833− cx.editor.set_status("Already at oldest change".to_owned());
4246+ cx.editor.set_status("Already at oldest change".to_owned());
3837fn redo(cx: &mut Context) {
4252fn redo(cx: &mut Context) {
4253+ let count = cx.count();
3838 let (view, doc) = current!(cx.editor);
4254 let (view, doc) = current!(cx.editor);
3839− let view_id = view.id;
4255+ for _ in 0..count {
3840− let success = doc.redo(view_id);
4256+ if !doc.redo(view.id) {
3841− if !success {
4257+ cx.editor.set_status("Already at newest change".to_owned());
4263+fn earlier(cx: &mut Context) {
4264+ let count = cx.count();
4265+ let (view, doc) = current!(cx.editor);
4266+ for _ in 0..count {
4267+ // rather than doing in batch we do this so get error halfway
4268+ if !doc.earlier(view.id, UndoKind::Steps(1)) {
4269+ cx.editor.set_status("Already at oldest change".to_owned());
4275+fn later(cx: &mut Context) {
4276+ let count = cx.count();
4277+ let (view, doc) = current!(cx.editor);
4278+ for _ in 0..count {
4279+ // rather than doing in batch we do this so get error halfway
4280+ if !doc.later(view.id, UndoKind::Steps(1)) {
3842− cx.editor.set_status("Already at newest change".to_owned());
4281+ cx.editor.set_status("Already at newest change".to_owned());
@@ -4297,12 +4738,12 @@ fn join_selections(cx: &mut Context) {
4297 doc.append_changes_to_history(view.id);
4738 doc.append_changes_to_history(view.id);
4300−fn keep_selections(cx: &mut Context) {
4741+fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) {
4301− // keep selections matching regex
4742+ // keep or remove selections matching regex
4302 let reg = cx.register.unwrap_or('/');
4743 let reg = cx.register.unwrap_or('/');
4303 let prompt = ui::regex_prompt(
4744 let prompt = ui::regex_prompt(
4305− "keep:".into(),
4746+ if !remove { "keep:" } else { "remove:" }.into(),
4306 Some(reg),
4747 Some(reg),
4307 |_input: &str| Vec::new(),
4748 |_input: &str| Vec::new(),
4308 move |view, doc, regex, event| {
4749 move |view, doc, regex, event| {
@@ -4311,7 +4752,9 @@ fn keep_selections(cx: &mut Context) {
4312 let text = doc.text().slice(..);
4753 let text = doc.text().slice(..);
4755+ if let Some(selection) =
4314− if let Some(selection) = selection::keep_matches(text, doc.selection(view.id), ®ex) {
4756+ selection::keep_or_remove_matches(text, doc.selection(view.id), ®ex, remove)
4315 doc.set_selection(view.id, selection);
4758 doc.set_selection(view.id, selection);
@@ -4320,6 +4763,14 @@ fn keep_selections(cx: &mut Context) {
4320 cx.push_layer(Box::new(prompt));
4763 cx.push_layer(Box::new(prompt));
4766+fn keep_selections(cx: &mut Context) {
4767+ keep_or_remove_selections_impl(cx, false)
4770+fn remove_selections(cx: &mut Context) {
4771+ keep_or_remove_selections_impl(cx, true)
4323fn keep_primary_selection(cx: &mut Context) {
4774fn keep_primary_selection(cx: &mut Context) {
4324 let (view, doc) = current!(cx.editor);
4775 let (view, doc) = current!(cx.editor);
4325 // TODO: handle count
4776 // TODO: handle count
@@ -4445,19 +4896,15 @@ pub fn completion(cx: &mut Context) {
4447 let size = compositor.size();
4898 let size = compositor.size();
4448− let ui = compositor
4899+ let ui = compositor.find::<ui::EditorView>().unwrap();
4449− .find(std::any::type_name::<ui::EditorView>())
4451− if let Some(ui) = ui.as_any_mut().downcast_mut::<ui::EditorView>() {
4452− ui.set_completion(
4900+ ui.set_completion(
4453− editor,
4901+ editor,
4455− offset_encoding,
4903+ offset_encoding,
4456− start_offset,
4904+ start_offset,
4457− trigger_offset,
4905+ trigger_offset,
@@ -4487,18 +4934,27 @@ fn hover(cx: &mut Context) {
4487 move |editor: &mut Editor, compositor: &mut Compositor, response: Option<lsp::Hover>| {
4934 move |editor: &mut Editor, compositor: &mut Compositor, response: Option<lsp::Hover>| {
4488 if let Some(hover) = response {
4935 if let Some(hover) = response {
4489 // hover.contents / .range <- used for visualizing
4936 // hover.contents / .range <- used for visualizing
4938+ fn marked_string_to_markdown(contents: lsp::MarkedString) -> String {
4490− let contents = match hover.contents {
4939+ match contents {
4491− lsp::HoverContents::Scalar(contents) => {
4940+ lsp::MarkedString::String(contents) => contents,
4492− // markedstring(string/languagestring to be highlighted)
4941+ lsp::MarkedString::LanguageString(string) => {
4493− // TODO
4942+ if string.language == "markdown" {
4494− log::error!("hover contents {:?}", contents);
4943+ string.value
4495− return;
4944+ } else {
4496− }
4945+ format!("```{}\n{}\n```", string.language, string.value)
4497− lsp::HoverContents::Array(contents) => {
4946+ }
4498− log::error!("hover contents {:?}", contents);
4947+ }
4501− // TODO: render markdown
4949+ }
4951+ let contents = match hover.contents {
4952+ lsp::HoverContents::Scalar(contents) => marked_string_to_markdown(contents),
4953+ lsp::HoverContents::Array(contents) => contents
4955+ .map(marked_string_to_markdown)
4956+ .collect::<Vec<_>>()
4502 lsp::HoverContents::Markup(contents) => contents.value,
4958 lsp::HoverContents::Markup(contents) => contents.value,
@@ -4706,7 +5162,21 @@ fn wclose(cx: &mut Context) {
4707 let view_id = view!(cx.editor).id;
5163 let view_id = view!(cx.editor).id;
4708 // close current split
5164 // close current split
4709− cx.editor.close(view_id, /* close_buffer */ false);
5165+ cx.editor.close(view_id);
5168+fn wonly(cx: &mut Context) {
5173+ .map(|(v, focus)| (v.id, focus))
5174+ .collect::<Vec<_>>();
5175+ for (view_id, focus) in views {
5177+ cx.editor.close(view_id);
4712fn select_register(cx: &mut Context) {
5182fn select_register(cx: &mut Context) {
@@ -4717,6 +5187,15 @@ fn select_register(cx: &mut Context) {
5190+fn insert_register(cx: &mut Context) {
5191+ cx.on_next_key(move |cx, event| {
5192+ if let Some(ch) = event.char() {
5193+ cx.editor.selected_register = Some(ch);
4720fn align_view_top(cx: &mut Context) {
5199fn align_view_top(cx: &mut Context) {
4721 let (view, doc) = current!(cx.editor);
5200 let (view, doc) = current!(cx.editor);
4722 align_view(doc, view, Align::Top);
5201 align_view(doc, view, Align::Top);
@@ -4785,10 +5264,19 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
4786 let selection = doc.selection(view.id).clone().transform(|range| {
5265 let selection = doc.selection(view.id).clone().transform(|range| {
4787 match ch {
5266 match ch {
4788− 'w' => textobject::textobject_word(text, range, objtype, count),
5267+ 'w' => textobject::textobject_word(text, range, objtype, count, false),
5268+ 'W' => textobject::textobject_word(text, range, objtype, count, true),
4789 'c' => textobject_treesitter("class", range),
5269 'c' => textobject_treesitter("class", range),
4790 'f' => textobject_treesitter("function", range),
5270 'f' => textobject_treesitter("function", range),
4791 'p' => textobject_treesitter("parameter", range),
5271 'p' => textobject_treesitter("parameter", range),
5273+ let ch = text.char(range.cursor(text));
5274+ if !ch.is_ascii_alphanumeric() {
5275+ textobject::textobject_surround(text, range, objtype, ch, count)
4792 // TODO: cancel new ranges if inconsistent surround matches across lines
5280 // TODO: cancel new ranges if inconsistent surround matches across lines
4793 ch if !ch.is_ascii_alphanumeric() => {
5281 ch if !ch.is_ascii_alphanumeric() => {
4794 textobject::textobject_surround(text, range, objtype, ch, count)
5282 textobject::textobject_surround(text, range, objtype, ch, count)
@@ -5051,6 +5539,10 @@ fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) {
5051 doc.apply(&transaction, view.id);
5539 doc.apply(&transaction, view.id);
5052 doc.append_changes_to_history(view.id);
5540 doc.append_changes_to_history(view.id);
5543+ // after replace cursor may be out of bounds, do this to
5544+ // make sure cursor is in view and update scroll as well
5545+ view.ensure_cursor_in_view(doc, cx.editor.config.scrolloff);
@@ -5096,3 +5588,76 @@ fn add_newline_impl(cx: &mut Context, open: Open) {
5096 doc.apply(&transaction, view.id);
5588 doc.apply(&transaction, view.id);
5097 doc.append_changes_to_history(view.id);
5589 doc.append_changes_to_history(view.id);
5592+fn rename_symbol(cx: &mut Context) {
5593+ let prompt = Prompt::new(
5594+ "rename-to:".into(),
5596+ |_input: &str| Vec::new(),
5597+ move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
5598+ if event != PromptEvent::Validate {
5602+ log::debug!("renaming to: {:?}", input);
5604+ let (view, doc) = current!(cx.editor);
5605+ let language_server = match doc.language_server() {
5606+ Some(language_server) => language_server,
5610+ let offset_encoding = language_server.offset_encoding();
5612+ let pos = pos_to_lsp_pos(
5614+ doc.selection(view.id)
5616+ .cursor(doc.text().slice(..)),
5620+ let task = language_server.rename_symbol(doc.identifier(), pos, input.to_string());
5621+ let edits = block_on(task).unwrap_or_default();
5622+ log::debug!("Edits from LSP: {:?}", edits);
5623+ apply_workspace_edit(&mut cx.editor, offset_encoding, &edits);
5627+ cx.push_layer(Box::new(prompt));
5630+/// Increment object under cursor by count.
5631+fn increment(cx: &mut Context) {
5632+ increment_impl(cx, cx.count() as i64);
5635+/// Decrement object under cursor by count.
5636+fn decrement(cx: &mut Context) {
5637+ increment_impl(cx, -(cx.count() as i64));
5640+/// Decrement object under cursor by `amount`.
5641+fn increment_impl(cx: &mut Context, amount: i64) {
5642+ let (view, doc) = current!(cx.editor);
5643+ let selection = doc.selection(view.id);
5644+ let text = doc.text();
5646+ let changes = selection.ranges().iter().filter_map(|range| {
5647+ let incrementor = NumberIncrementor::from_range(text.slice(..), *range)?;
5648+ let new_text = incrementor.incremented_text(amount);
5650+ incrementor.range.from(),
5651+ incrementor.range.to(),
5656+ if changes.clone().count() > 0 {
5657+ let transaction = Transaction::change(doc.text(), changes);
5658+ let transaction = transaction.with_selection(selection.clone());
5660+ doc.apply(&transaction, view.id);
5661+ doc.append_changes_to_history(view.id);