@@ -26,6 +26,7 @@ use helix_view::{
2626};
2727
2828use anyhow::{anyhow, bail, ensure, Context as _};
29+use fuzzy_matcher::FuzzyMatcher;
2930use helix_lsp::{
3031 block_on, lsp,
3132 util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
@@ -266,6 +267,7 @@ impl MappableCommand {
266267 change_selection_noyank, "Change selection (delete and enter insert mode, without yanking)",
267268 collapse_selection, "Collapse selection onto a single cursor",
268269 flip_selections, "Flip selection cursor and anchor",
270+ ensure_selections_forward, "Ensure the selection is in forward direction",
269271 insert_mode, "Insert before selection",
270272 append_mode, "Insert after selection (append)",
271273 command_mode, "Enter command mode",
@@ -287,7 +289,7 @@ impl MappableCommand {
287289 add_newline_below, "Add newline below",
288290 goto_type_definition, "Goto type definition",
289291 goto_implementation, "Goto implementation",
290− goto_file_start, "Goto file start/line",
292+ goto_file_start, "Goto line number <n> else file start",
291293 goto_file_end, "Goto file end",
292294 goto_file, "Goto files in selection",
293295 goto_file_hsplit, "Goto files in selection (hsplit)",
@@ -360,6 +362,7 @@ impl MappableCommand {
360362 rotate_selection_contents_forward, "Rotate selection contents forward",
361363 rotate_selection_contents_backward, "Rotate selections contents backward",
362364 expand_selection, "Expand selection to parent syntax node",
365+ shrink_selection, "Shrink selection to previously expanded syntax node",
363366 jump_forward, "Jump forward on jumplist",
364367 jump_backward, "Jump backward on jumplist",
365368 save_selection, "Save the current selection to the jumplist",
@@ -396,7 +399,7 @@ impl MappableCommand {
396399 increment, "Increment",
397400 decrement, "Decrement",
398401 record_macro, "Record macro",
399− play_macro, "Play macro",
402+ replay_macro, "Replay macro",
400403 );
401404}
402405
@@ -1280,16 +1283,23 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
12801283 .max(view.offset.row + scrolloff)
12811284 .min(last_line.saturating_sub(scrolloff));
12821285
1283− let head = pos_at_coords(text, Position::new(line, cursor.col), true); // this func will properly truncate to line end
1286+ // If cursor needs moving, replace primary selection
1287+ if line != cursor.row {
1288+ let head = pos_at_coords(text, Position::new(line, cursor.col), true); // this func will properly truncate to line end
12841289
1285− let anchor = if doc.mode == Mode::Select {
1286− range.anchor
1287− } else {
1288− head
1289− };
1290+ let anchor = if doc.mode == Mode::Select {
1291+ range.anchor
1292+ } else {
1293+ head
1294+ };
12901295
1291− // TODO: only manipulate main selection
1292− doc.set_selection(view.id, Selection::single(anchor, head));
1296+ // replace primary selection with an empty selection at cursor pos
1297+ let prim_sel = Range::new(anchor, head);
1298+ let mut sel = doc.selection(view.id).clone();
1299+ let idx = sel.primary_index();
1300+ sel = sel.replace(idx, prim_sel);
1301+ doc.set_selection(view.id, sel);
1302+ }
12931303}
12941304
12951305fn page_up(cx: &mut Context) {
@@ -1543,7 +1553,7 @@ fn searcher(cx: &mut Context, direction: Direction) {
15431553 let reg = cx.register.unwrap_or('/');
15441554 let scrolloff = cx.editor.config.scrolloff;
15451555
1546− let (_, doc) = current!(cx.editor);
1556+ let doc = doc!(cx.editor);
15471557
15481558 // TODO: could probably share with select_on_matches?
15491559
@@ -1630,7 +1640,7 @@ fn search_selection(cx: &mut Context) {
16301640 let query = doc.selection(view.id).primary().fragment(contents);
16311641 let regex = regex::escape(&query);
16321642 cx.editor.registers.get_mut('/').push(regex);
1633− let msg = format!("register '{}' set to '{}'", '\\', query);
1643+ let msg = format!("register '{}' set to '{}'", '/', query);
16341644 cx.editor.set_status(msg);
16351645}
16361646
@@ -1904,7 +1914,21 @@ fn flip_selections(cx: &mut Context) {
19041914 let selection = doc
19051915 .selection(view.id)
19061916 .clone()
1907− .transform(|range| Range::new(range.head, range.anchor));
1917+ .transform(|range| range.flip());
1918+ doc.set_selection(view.id, selection);
1919+}
1920+
1921+fn ensure_selections_forward(cx: &mut Context) {
1922+ let (view, doc) = current!(cx.editor);
1923+
1924+ let selection = doc
1925+ .selection(view.id)
1926+ .clone()
1927+ .transform(|r| match r.direction() {
1928+ Direction::Forward => r,
1929+ Direction::Backward => r.flip(),
1930+ });
1931+
19081932 doc.set_selection(view.id, selection);
19091933}
19101934
@@ -1938,7 +1962,7 @@ fn append_mode(cx: &mut Context) {
19381962 if !last_range.is_empty() && last_range.head == end {
19391963 let transaction = Transaction::change(
19401964 doc.text(),
1941− std::array::IntoIter::new([(end, end, Some(doc.line_ending.as_str().into()))]),
1965+ [(end, end, Some(doc.line_ending.as_str().into()))].into_iter(),
19421966 );
19431967 doc.apply(&transaction, view.id);
19441968 }
@@ -2030,7 +2054,7 @@ pub mod cmd {
20302054
20312055 fn write_impl(cx: &mut compositor::Context, path: Option<&Cow<str>>) -> anyhow::Result<()> {
20322056 let jobs = &mut cx.jobs;
2033− let (_, doc) = current!(cx.editor);
2057+ let doc = doc_mut!(cx.editor);
20342058
20352059 if let Some(ref path) = path {
20362060 doc.set_path(Some(path.as_ref().as_ref()))
@@ -2083,8 +2107,7 @@ pub mod cmd {
20832107 _args: &[Cow<str>],
20842108 _event: PromptEvent,
20852109 ) -> anyhow::Result<()> {
2086− let (_, doc) = current!(cx.editor);
2087−
2110+ let doc = doc!(cx.editor);
20882111 if let Some(format) = doc.format() {
20892112 let callback =
20902113 make_format_callback(doc.id(), doc.version(), Modified::LeaveModified, format);
@@ -2307,12 +2330,7 @@ pub mod cmd {
23072330 write_all_impl(cx, args, event, true, true)
23082331 }
23092332
2310− fn quit_all_impl(
2311− editor: &mut Editor,
2312− _args: &[Cow<str>],
2313− _event: PromptEvent,
2314− force: bool,
2315− ) -> anyhow::Result<()> {
2333+ fn quit_all_impl(editor: &mut Editor, force: bool) -> anyhow::Result<()> {
23162334 if !force {
23172335 buffers_remaining_impl(editor)?;
23182336 }
@@ -2328,18 +2346,18 @@ pub mod cmd {
23282346
23292347 fn quit_all(
23302348 cx: &mut compositor::Context,
2331− args: &[Cow<str>],
2332− event: PromptEvent,
2349+ _args: &[Cow<str>],
2350+ _event: PromptEvent,
23332351 ) -> anyhow::Result<()> {
2334− quit_all_impl(cx.editor, args, event, false)
2352+ quit_all_impl(cx.editor, false)
23352353 }
23362354
23372355 fn force_quit_all(
23382356 cx: &mut compositor::Context,
2339− args: &[Cow<str>],
2340− event: PromptEvent,
2357+ _args: &[Cow<str>],
2358+ _event: PromptEvent,
23412359 ) -> anyhow::Result<()> {
2342− quit_all_impl(cx.editor, args, event, true)
2360+ quit_all_impl(cx.editor, true)
23432361 }
23442362
23452363 fn cquit(
@@ -2353,12 +2371,21 @@ pub mod cmd {
23532371 .unwrap_or(1);
23542372 cx.editor.exit_code = exit_code;
23552373
2356− let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect();
2357− for view_id in views {
2358− cx.editor.close(view_id);
2359− }
2374+ quit_all_impl(cx.editor, false)
2375+ }
23602376
2361− Ok(())
2377+ fn force_cquit(
2378+ cx: &mut compositor::Context,
2379+ args: &[Cow<str>],
2380+ _event: PromptEvent,
2381+ ) -> anyhow::Result<()> {
2382+ let exit_code = args
2383+ .first()
2384+ .and_then(|code| code.parse::<i32>().ok())
2385+ .unwrap_or(1);
2386+ cx.editor.exit_code = exit_code;
2387+
2388+ quit_all_impl(cx.editor, true)
23622389 }
23632390
23642391 fn theme(
@@ -2393,7 +2420,7 @@ pub mod cmd {
23932420 args: &[Cow<str>],
23942421 _event: PromptEvent,
23952422 ) -> anyhow::Result<()> {
2396− let (_, doc) = current!(cx.editor);
2423+ let doc = doc!(cx.editor);
23972424 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
23982425 let separator = args.first().unwrap_or(&default_sep);
23992426 yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Clipboard)
@@ -2412,7 +2439,7 @@ pub mod cmd {
24122439 args: &[Cow<str>],
24132440 _event: PromptEvent,
24142441 ) -> anyhow::Result<()> {
2415− let (_, doc) = current!(cx.editor);
2442+ let doc = doc!(cx.editor);
24162443 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
24172444 let separator = args.first().unwrap_or(&default_sep);
24182445 yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Selection)
@@ -2539,7 +2566,7 @@ pub mod cmd {
25392566 args: &[Cow<str>],
25402567 _event: PromptEvent,
25412568 ) -> anyhow::Result<()> {
2542− let (_, doc) = current!(cx.editor);
2569+ let doc = doc_mut!(cx.editor);
25432570 if let Some(label) = args.first() {
25442571 doc.set_encoding(label)
25452572 } else {
@@ -2637,6 +2664,86 @@ pub mod cmd {
26372664 let (view, doc) = current!(cx.editor);
26382665
26392666 view.ensure_cursor_in_view(doc, line);
2667+ Ok(())
2668+ }
2669+
2670+ fn setting(
2671+ cx: &mut compositor::Context,
2672+ args: &[Cow<str>],
2673+ _event: PromptEvent,
2674+ ) -> anyhow::Result<()> {
2675+ let runtime_config = &mut cx.editor.config;
2676+
2677+ if args.len() != 2 {
2678+ anyhow::bail!("Bad arguments. Usage: `:set key field`");
2679+ }
2680+
2681+ let (key, arg) = (&args[0].to_lowercase(), &args[1]);
2682+
2683+ match key.as_ref() {
2684+ "scrolloff" => runtime_config.scrolloff = arg.parse()?,
2685+ "scroll-lines" => runtime_config.scroll_lines = arg.parse()?,
2686+ "mouse" => runtime_config.mouse = arg.parse()?,
2687+ "line-number" => runtime_config.line_number = arg.parse()?,
2688+ "middle-click_paste" => runtime_config.middle_click_paste = arg.parse()?,
2689+ "smart-case" => runtime_config.smart_case = arg.parse()?,
2690+ "auto-pairs" => runtime_config.auto_pairs = arg.parse()?,
2691+ "auto-completion" => runtime_config.auto_completion = arg.parse()?,
2692+ "completion-trigger-len" => runtime_config.completion_trigger_len = arg.parse()?,
2693+ "auto-info" => runtime_config.auto_info = arg.parse()?,
2694+ "true-color" => runtime_config.true_color = arg.parse()?,
2695+ _ => anyhow::bail!("Unknown key `{}`.", args[0]),
2696+ }
2697+
2698+ Ok(())
2699+ }
2700+
2701+ fn sort(
2702+ cx: &mut compositor::Context,
2703+ args: &[Cow<str>],
2704+ _event: PromptEvent,
2705+ ) -> anyhow::Result<()> {
2706+ sort_impl(cx, args, false)
2707+ }
2708+
2709+ fn sort_reverse(
2710+ cx: &mut compositor::Context,
2711+ args: &[Cow<str>],
2712+ _event: PromptEvent,
2713+ ) -> anyhow::Result<()> {
2714+ sort_impl(cx, args, true)
2715+ }
2716+
2717+ fn sort_impl(
2718+ cx: &mut compositor::Context,
2719+ _args: &[Cow<str>],
2720+ reverse: bool,
2721+ ) -> anyhow::Result<()> {
2722+ let (view, doc) = current!(cx.editor);
2723+ let text = doc.text().slice(..);
2724+
2725+ let selection = doc.selection(view.id);
2726+
2727+ let mut fragments: Vec<_> = selection
2728+ .fragments(text)
2729+ .map(|fragment| Tendril::from_slice(&fragment))
2730+ .collect();
2731+
2732+ fragments.sort_by(match reverse {
2733+ true => |a: &Tendril, b: &Tendril| b.cmp(a),
2734+ false => |a: &Tendril, b: &Tendril| a.cmp(b),
2735+ });
2736+
2737+ let transaction = Transaction::change(
2738+ doc.text(),
2739+ selection
2740+ .into_iter()
2741+ .zip(fragments)
2742+ .map(|(s, fragment)| (s.from(), s.to(), Some(fragment))),
2743+ );
2744+
2745+ doc.apply(&transaction, view.id);
2746+ doc.append_changes_to_history(view.id);
26402747
26412748 Ok(())
26422749 }
@@ -2664,18 +2771,18 @@ pub mod cmd {
26642771 completer: Some(completers::filename),
26652772 },
26662773 TypableCommand {
2667− name: "buffer-close",
2668− aliases: &["bc", "bclose"],
2669− doc: "Close the current buffer.",
2670− fun: buffer_close,
2671− completer: None, // FIXME: buffer completer
2774+ name: "buffer-close",
2775+ aliases: &["bc", "bclose"],
2776+ doc: "Close the current buffer.",
2777+ fun: buffer_close,
2778+ completer: None, // FIXME: buffer completer
26722779 },
26732780 TypableCommand {
2674− name: "buffer-close!",
2675− aliases: &["bc!", "bclose!"],
2676− doc: "Close the current buffer forcefully (ignoring unsaved changes).",
2677− fun: force_buffer_close,
2678− completer: None, // FIXME: buffer completer
2781+ name: "buffer-close!",
2782+ aliases: &["bc!", "bclose!"],
2783+ doc: "Close the current buffer forcefully (ignoring unsaved changes).",
2784+ fun: force_buffer_close,
2785+ completer: None, // FIXME: buffer completer
26792786 },
26802787 TypableCommand {
26812788 name: "write",
@@ -2782,6 +2889,13 @@ pub mod cmd {
27822889 fun: cquit,
27832890 completer: None,
27842891 },
2892+ TypableCommand {
2893+ name: "cquit!",
2894+ aliases: &["cq!"],
2895+ doc: "Quit with exit code (default 1) forcefully (ignoring unsaved changes). Accepts an optional integer exit code (:cq! 2).",
2896+ fun: force_cquit,
2897+ completer: None,
2898+ },
27852899 TypableCommand {
27862900 name: "theme",
27872901 aliases: &[],
@@ -2928,7 +3042,28 @@ pub mod cmd {
29283042 doc: "Go to line number.",
29293043 fun: goto_line_number,
29303044 completer: None,
2931− }
3045+ },
3046+ TypableCommand {
3047+ name: "set-option",
3048+ aliases: &["set"],
3049+ doc: "Set a config option at runtime",
3050+ fun: setting,
3051+ completer: Some(completers::setting),
3052+ },
3053+ TypableCommand {
3054+ name: "sort",
3055+ aliases: &[],
3056+ doc: "Sort ranges in selection.",
3057+ fun: sort,
3058+ completer: None,
3059+ },
3060+ TypableCommand {
3061+ name: "rsort",
3062+ aliases: &[],
3063+ doc: "Sort ranges in selection in reverse order.",
3064+ fun: sort_reverse,
3065+ completer: None,
3066+ },
29323067 ];
29333068
29343069 pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
@@ -2948,17 +3083,28 @@ fn command_mode(cx: &mut Context) {
29483083 ":".into(),
29493084 Some(':'),
29503085 |input: &str| {
3086+ static FUZZY_MATCHER: Lazy<fuzzy_matcher::skim::SkimMatcherV2> =
3087+ Lazy::new(fuzzy_matcher::skim::SkimMatcherV2::default);
3088+
29513089 // we use .this over split_whitespace() because we care about empty segments
29523090 let parts = input.split(' ').collect::<Vec<&str>>();
29533091
29543092 // simple heuristic: if there's no just one part, complete command name.
29553093 // if there's a space, per command completion kicks in.
29563094 if parts.len() <= 1 {
2957− let end = 0..;
2958− cmd::TYPABLE_COMMAND_LIST
3095+ let mut matches: Vec<_> = cmd::TYPABLE_COMMAND_LIST
29593096 .iter()
2960− .filter(|command| command.name.contains(input))
2961− .map(|command| (end.clone(), Cow::Borrowed(command.name)))
3097+ .filter_map(|command| {
3098+ FUZZY_MATCHER
3099+ .fuzzy_match(command.name, input)
3100+ .map(|score| (command.name, score))
3101+ })
3102+ .collect();
3103+
3104+ matches.sort_unstable_by_key(|(_file, score)| std::cmp::Reverse(*score));
3105+ matches
3106+ .into_iter()
3107+ .map(|(name, _)| (0.., name.into()))
29623108 .collect()
29633109 } else {
29643110 let part = parts.last().unwrap();
@@ -3002,7 +3148,16 @@ fn command_mode(cx: &mut Context) {
30023148
30033149 // Handle typable commands
30043150 if let Some(cmd) = cmd::TYPABLE_COMMAND_MAP.get(parts[0]) {
3005− let args = shellwords::shellwords(input);
3151+ let args = if cfg!(unix) {
3152+ shellwords::shellwords(input)
3153+ } else {
3154+ // Windows doesn't support POSIX, so fallback for now
3155+ parts
3156+ .into_iter()
3157+ .map(|part| part.into())
3158+ .collect::<Vec<_>>()
3159+ };
3160+
30063161 if let Err(e) = (cmd.fun)(cx, &args[1..], event) {
30073162 cx.editor.set_error(format!("{}", e));
30083163 }
@@ -3026,7 +3181,8 @@ fn command_mode(cx: &mut Context) {
30263181}
30273182
30283183fn file_picker(cx: &mut Context) {
3029− let root = find_root(None).unwrap_or_else(|| PathBuf::from("./"));
3184+ // We don't specify language markers, root will be the root of the current git repo
3185+ let root = find_root(None, &[]).unwrap_or_else(|| PathBuf::from("./"));
30303186 let picker = ui::file_picker(root, &cx.editor.config);
30313187 cx.push_layer(Box::new(picker));
30323188}
@@ -3118,7 +3274,7 @@ fn symbol_picker(cx: &mut Context) {
31183274 nested_to_flat(list, file, child);
31193275 }
31203276 }
3121− let (_, doc) = current!(cx.editor);
3277+ let doc = doc!(cx.editor);
31223278
31233279 let language_server = match doc.language_server() {
31243280 Some(language_server) => language_server,
@@ -3139,7 +3295,7 @@ fn symbol_picker(cx: &mut Context) {
31393295 let symbols = match symbols {
31403296 lsp::DocumentSymbolResponse::Flat(symbols) => symbols,
31413297 lsp::DocumentSymbolResponse::Nested(symbols) => {
3142− let (_view, doc) = current!(editor);
3298+ let doc = doc!(editor);
31433299 let mut flat_symbols = Vec::new();
31443300 for symbol in symbols {
31453301 nested_to_flat(&mut flat_symbols, &doc.identifier(), symbol)
@@ -3181,17 +3337,15 @@ fn symbol_picker(cx: &mut Context) {
31813337}
31823338
31833339fn workspace_symbol_picker(cx: &mut Context) {
3184− let (_, doc) = current!(cx.editor);
3185−
3340+ let doc = doc!(cx.editor);
3341+ let current_path = doc.path().cloned();
31863342 let language_server = match doc.language_server() {
31873343 Some(language_server) => language_server,
31883344 None => return,
31893345 };
31903346 let offset_encoding = language_server.offset_encoding();
3191−
31923347 let future = language_server.workspace_symbols("".to_string());
31933348
3194− let current_path = doc_mut!(cx.editor).path().cloned();
31953349 cx.callback(
31963350 future,
31973351 move |_editor: &mut Editor,
@@ -3277,12 +3431,19 @@ pub fn code_action(cx: &mut Context) {
32773431 move |editor, code_action, _action| match code_action {
32783432 lsp::CodeActionOrCommand::Command(command) => {
32793433 log::debug!("code action command: {:?}", command);
3280− editor.set_error(String::from("Handling code action command is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
3434+ execute_lsp_command(editor, command.clone());
32813435 }
32823436 lsp::CodeActionOrCommand::CodeAction(code_action) => {
32833437 log::debug!("code action: {:?}", code_action);
32843438 if let Some(ref workspace_edit) = code_action.edit {
3285− apply_workspace_edit(editor, offset_encoding, workspace_edit)
3439+ log::debug!("edit: {:?}", workspace_edit);
3440+ apply_workspace_edit(editor, offset_encoding, workspace_edit);
3441+ }
3442+
3443+ // if code action provides both edit and command first the edit
3444+ // should be applied and then the command
3445+ if let Some(command) = &code_action.command {
3446+ execute_lsp_command(editor, command.clone());
32863447 }
32873448 }
32883449 },
@@ -3293,6 +3454,25 @@ pub fn code_action(cx: &mut Context) {
32933454 )
32943455}
32953456
3457+pub fn execute_lsp_command(editor: &mut Editor, cmd: lsp::Command) {
3458+ let doc = doc!(editor);
3459+ let language_server = match doc.language_server() {
3460+ Some(language_server) => language_server,
3461+ None => return,
3462+ };
3463+
3464+ // the command is executed on the server and communicated back
3465+ // to the client asynchronously using workspace edits
3466+ let command_future = language_server.command(cmd);
3467+ tokio::spawn(async move {
3468+ let res = command_future.await;
3469+
3470+ if let Err(e) = res {
3471+ log::error!("execute LSP command: {}", e);
3472+ }
3473+ });
3474+}
3475+
32963476pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
32973477 use lsp::ResourceOp;
32983478 use std::fs;
@@ -3346,7 +3526,7 @@ pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
33463526 }
33473527}
33483528
3349−fn apply_workspace_edit(
3529+pub fn apply_workspace_edit(
33503530 editor: &mut Editor,
33513531 offset_encoding: OffsetEncoding,
33523532 workspace_edit: &lsp::WorkspaceEdit,
@@ -3537,22 +3717,22 @@ fn open(cx: &mut Context, open: Open) {
35373717 let mut offs = 0;
35383718
35393719 let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
3540− let line = range.cursor_line(text);
3720+ let cursor_line = range.cursor_line(text);
35413721
3542− let line = match open {
3722+ let new_line = match open {
35433723 // adjust position to the end of the line (next line - 1)
3544− Open::Below => line + 1,
3724+ Open::Below => cursor_line + 1,
35453725 // adjust position to the end of the previous line (current line - 1)
3546− Open::Above => line,
3726+ Open::Above => cursor_line,
35473727 };
35483728
35493729 // Index to insert newlines after, as well as the char width
35503730 // to use to compensate for those inserted newlines.
3551− let (line_end_index, line_end_offset_width) = if line == 0 {
3731+ let (line_end_index, line_end_offset_width) = if new_line == 0 {
35523732 (0, 0)
35533733 } else {
35543734 (
3555− line_end_char_index(&doc.text().slice(..), line.saturating_sub(1)),
3735+ line_end_char_index(&doc.text().slice(..), new_line.saturating_sub(1)),
35563736 doc.line_ending.len_chars(),
35573737 )
35583738 };
@@ -3563,8 +3743,10 @@ fn open(cx: &mut Context, open: Open) {
35633743 doc.syntax(),
35643744 text,
35653745 line_end_index,
3746+ new_line.saturating_sub(1),
35663747 true,
3567− );
3748+ )
3749+ .unwrap_or_else(|| indent::indent_level_for_line(text.line(cursor_line), doc.tab_width()));
35683750 let indent = doc.indent_unit().repeat(indent_level);
35693751 let indent_len = indent.len();
35703752 let mut text = String::with_capacity(1 + indent_len);
@@ -3610,6 +3792,7 @@ fn normal_mode(cx: &mut Context) {
36103792
36113793 doc.mode = Mode::Normal;
36123794
3795+ try_restore_indent(doc, view.id);
36133796 doc.append_changes_to_history(view.id);
36143797
36153798 // if leaving append mode, move cursor back by 1
@@ -3627,6 +3810,40 @@ fn normal_mode(cx: &mut Context) {
36273810 }
36283811}
36293812
3813+fn try_restore_indent(doc: &mut Document, view_id: ViewId) {
3814+ use helix_core::chars::char_is_whitespace;
3815+ use helix_core::Operation;
3816+
3817+ fn inserted_a_new_blank_line(changes: &[Operation], pos: usize, line_end_pos: usize) -> bool {
3818+ if let [Operation::Retain(move_pos), Operation::Insert(ref inserted_str), Operation::Retain(_)] =
3819+ changes
3820+ {
3821+ move_pos + inserted_str.len32() as usize == pos
3822+ && inserted_str.starts_with('\n')
3823+ && inserted_str.chars().skip(1).all(char_is_whitespace)
3824+ && pos == line_end_pos // ensure no characters exists after current position
3825+ } else {
3826+ false
3827+ }
3828+ }
3829+
3830+ let doc_changes = doc.changes().changes();
3831+ let text = doc.text().slice(..);
3832+ let range = doc.selection(view_id).primary();
3833+ let pos = range.cursor(text);
3834+ let line_end_pos = line_end_char_index(&text, range.cursor_line(text));
3835+
3836+ if inserted_a_new_blank_line(doc_changes, pos, line_end_pos) {
3837+ // Removes tailing whitespaces.
3838+ let transaction =
3839+ Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
3840+ let line_start_pos = text.line_to_char(range.cursor_line(text));
3841+ (line_start_pos, pos, None)
3842+ });
3843+ doc.apply(&transaction, view_id);
3844+ }
3845+}
3846+
36303847// Store a jump on the jumplist.
36313848fn push_jump(editor: &mut Editor) {
36323849 let (view, doc) = current!(editor);
@@ -3994,27 +4211,21 @@ fn goto_pos(editor: &mut Editor, pos: usize) {
39944211}
39954212
39964213fn goto_first_diag(cx: &mut Context) {
3997− let editor = &mut cx.editor;
3998− let (_, doc) = current!(editor);
3999−
4214+ let doc = doc!(cx.editor);
40004215 let pos = match doc.diagnostics().first() {
40014216 Some(diag) => diag.range.start,
40024217 None => return,
40034218 };
4004−
4005− goto_pos(editor, pos);
4219+ goto_pos(cx.editor, pos);
40064220}
40074221
40084222fn goto_last_diag(cx: &mut Context) {
4009− let editor = &mut cx.editor;
4010− let (_, doc) = current!(editor);
4011−
4223+ let doc = doc!(cx.editor);
40124224 let pos = match doc.diagnostics().last() {
40134225 Some(diag) => diag.range.start,
40144226 None => return,
40154227 };
4016−
4017− goto_pos(editor, pos);
4228+ goto_pos(cx.editor, pos);
40184229}
40194230
40204231fn goto_next_diag(cx: &mut Context) {
@@ -4270,48 +4481,48 @@ pub mod insert {
42704481 };
42714482 let curr = contents.get_char(pos).unwrap_or(' ');
42724483
4273− // TODO: offset range.head by 1? when calculating?
4484+ let current_line = text.char_to_line(pos);
42744485 let indent_level = indent::suggested_indent_for_pos(
42754486 doc.language_config(),
42764487 doc.syntax(),
42774488 text,
4278− pos.saturating_sub(1),
4489+ pos,
4490+ current_line,
42794491 true,
4280− );
4281− let indent = doc.indent_unit().repeat(indent_level);
4282− let mut text = String::with_capacity(1 + indent.len());
4283− text.push_str(doc.line_ending.as_str());
4284− text.push_str(&indent);
4492+ )
4493+ .unwrap_or_else(|| {
4494+ indent::indent_level_for_line(text.line(current_line), doc.tab_width())
4495+ });
42854496
4286− let head = pos + offs + text.chars().count();
4497+ let indent = doc.indent_unit().repeat(indent_level);
4498+ let mut text = String::new();
4499+ // If we are between pairs (such as brackets), we want to insert an additional line which is indented one level more and place the cursor there
4500+ let new_head_pos = if helix_core::auto_pairs::PAIRS.contains(&(prev, curr)) {
4501+ let inner_indent = doc.indent_unit().repeat(indent_level + 1);
4502+ text.reserve_exact(2 + indent.len() + inner_indent.len());
4503+ text.push_str(doc.line_ending.as_str());
4504+ text.push_str(&inner_indent);
4505+ let new_head_pos = pos + offs + text.chars().count();
4506+ text.push_str(doc.line_ending.as_str());
4507+ text.push_str(&indent);
4508+ new_head_pos
4509+ } else {
4510+ text.reserve_exact(1 + indent.len());
4511+ text.push_str(doc.line_ending.as_str());
4512+ text.push_str(&indent);
4513+ pos + offs + text.chars().count()
4514+ };
42874515
42884516 // TODO: range replace or extend
42894517 // range.replace(|range| range.is_empty(), head); -> fn extend if cond true, new head pos
42904518 // can be used with cx.mode to do replace or extend on most changes
4291− ranges.push(Range::new(
4292− if range.is_empty() {
4293− head
4294− } else {
4295− range.anchor + offs
4296− },
4297− head,
4298− ));
4299−
4300− // if between a bracket pair
4301− if helix_core::auto_pairs::PAIRS.contains(&(prev, curr)) {
4302− // another newline, indent the end bracket one level less
4303− let indent = doc.indent_unit().repeat(indent_level.saturating_sub(1));
4304− text.push_str(doc.line_ending.as_str());
4305− text.push_str(&indent);
4306− }
4307−
4519+ ranges.push(Range::new(new_head_pos, new_head_pos));
43084520 offs += text.chars().count();
43094521
43104522 (pos, pos, Some(text.into()))
43114523 });
43124524
43134525 transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
4314− //
43154526
43164527 doc.apply(&transaction, view.id);
43174528 }
@@ -5079,7 +5290,7 @@ pub fn completion(cx: &mut Context) {
50795290 move |editor: &mut Editor,
50805291 compositor: &mut Compositor,
50815292 response: Option<lsp::CompletionResponse>| {
5082− let (_, doc) = current!(editor);
5293+ let doc = doc!(editor);
50835294 if doc.mode() != Mode::Insert {
50845295 // we're not in insert mode anymore
50855296 return;
@@ -5257,6 +5468,7 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
52575468 doc.apply(&transaction, view.id);
52585469 doc.append_changes_to_history(view.id);
52595470}
5471+
52605472fn rotate_selection_contents_forward(cx: &mut Context) {
52615473 rotate_selection_contents(cx, Direction::Forward)
52625474}
@@ -5272,7 +5484,39 @@ fn expand_selection(cx: &mut Context) {
52725484
52735485 if let Some(syntax) = doc.syntax() {
52745486 let text = doc.text().slice(..);
5275− let selection = object::expand_selection(syntax, text, doc.selection(view.id));
5487+
5488+ let current_selection = doc.selection(view.id);
5489+
5490+ // save current selection so it can be restored using shrink_selection
5491+ view.object_selections.push(current_selection.clone());
5492+
5493+ let selection = object::expand_selection(syntax, text, current_selection);
5494+ doc.set_selection(view.id, selection);
5495+ }
5496+ };
5497+ motion(cx.editor);
5498+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
5499+}
5500+
5501+fn shrink_selection(cx: &mut Context) {
5502+ let motion = |editor: &mut Editor| {
5503+ let (view, doc) = current!(editor);
5504+ let current_selection = doc.selection(view.id);
5505+ // try to restore previous selection
5506+ if let Some(prev_selection) = view.object_selections.pop() {
5507+ if current_selection.contains(&prev_selection) {
5508+ // allow shrinking the selection only if current selection contains the previous object selection
5509+ doc.set_selection(view.id, prev_selection);
5510+ return;
5511+ } else {
5512+ // clear existing selection as they can't be shrinked to anyway
5513+ view.object_selections.clear();
5514+ }
5515+ }
5516+ // if not previous selection, shrink to first child
5517+ if let Some(syntax) = doc.syntax() {
5518+ let text = doc.text().slice(..);
5519+ let selection = object::shrink_selection(syntax, text, current_selection);
52765520 doc.set_selection(view.id, selection);
52775521 }
52785522 };
@@ -5920,42 +6164,42 @@ fn record_macro(cx: &mut Context) {
59206164 keys.pop();
59216165 let s = keys
59226166 .into_iter()
5923− .map(|key| format!("{}", key))
5924− .collect::<Vec<_>>()
5925− .join(" ");
6167+ .map(|key| {
6168+ let s = key.to_string();
6169+ if s.chars().count() == 1 {
6170+ s
6171+ } else {
6172+ format!("<{}>", s)
6173+ }
6174+ })
6175+ .collect::<String>();
59266176 cx.editor.registers.get_mut(reg).write(vec![s]);
59276177 cx.editor
5928− .set_status(format!("Recorded to register {}", reg));
6178+ .set_status(format!("Recorded to register [{}]", reg));
59296179 } else {
59306180 let reg = cx.register.take().unwrap_or('@');
59316181 cx.editor.macro_recording = Some((reg, Vec::new()));
59326182 cx.editor
5933− .set_status(format!("Recording to register {}", reg));
6183+ .set_status(format!("Recording to register [{}]", reg));
59346184 }
59356185}
59366186
5937−fn play_macro(cx: &mut Context) {
6187+fn replay_macro(cx: &mut Context) {
59386188 let reg = cx.register.unwrap_or('@');
5939− let keys = match cx
5940− .editor
5941− .registers
5942− .get(reg)
5943− .and_then(|reg| reg.read().get(0))
5944− .context("Register empty")
5945− .and_then(|s| {
5946− s.split_whitespace()
5947− .map(str::parse::<KeyEvent>)
5948− .collect::<Result<Vec<_>, _>>()
5949− .context("Failed to parse macro")
5950− }) {
5951− Ok(keys) => keys,
5952− Err(e) => {
5953− cx.editor.set_error(format!("{}", e));
5954− return;
6189+ let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) {
6190+ match helix_view::input::parse_macro(keys_str) {
6191+ Ok(keys) => keys,
6192+ Err(err) => {
6193+ cx.editor.set_error(format!("Invalid macro: {}", err));
6194+ return;
6195+ }
59556196 }
6197+ } else {
6198+ cx.editor.set_error(format!("Register [{}] empty", reg));
6199+ return;
59566200 };
5957− let count = cx.count();
59586201
6202+ let count = cx.count();
59596203 cx.callback = Some(Box::new(
59606204 move |compositor: &mut Compositor, cx: &mut compositor::Context| {
59616205 for _ in 0..count {