@@ -26,6 +26,7 @@ use helix_view::{
28use anyhow::{anyhow, bail, ensure, Context as _};
28use anyhow::{anyhow, bail, ensure, Context as _};
29+use fuzzy_matcher::FuzzyMatcher;
29use helix_lsp::{
30use helix_lsp::{
30 block_on, lsp,
31 block_on, lsp,
31 util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
32 util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
@@ -266,6 +267,7 @@ impl MappableCommand {
266 change_selection_noyank, "Change selection (delete and enter insert mode, without yanking)",
267 change_selection_noyank, "Change selection (delete and enter insert mode, without yanking)",
267 collapse_selection, "Collapse selection onto a single cursor",
268 collapse_selection, "Collapse selection onto a single cursor",
268 flip_selections, "Flip selection cursor and anchor",
269 flip_selections, "Flip selection cursor and anchor",
270+ ensure_selections_forward, "Ensure the selection is in forward direction",
269 insert_mode, "Insert before selection",
271 insert_mode, "Insert before selection",
270 append_mode, "Insert after selection (append)",
272 append_mode, "Insert after selection (append)",
271 command_mode, "Enter command mode",
273 command_mode, "Enter command mode",
@@ -287,7 +289,7 @@ impl MappableCommand {
287 add_newline_below, "Add newline below",
289 add_newline_below, "Add newline below",
288 goto_type_definition, "Goto type definition",
290 goto_type_definition, "Goto type definition",
289 goto_implementation, "Goto implementation",
291 goto_implementation, "Goto implementation",
290− goto_file_start, "Goto file start/line",
292+ goto_file_start, "Goto line number <n> else file start",
291 goto_file_end, "Goto file end",
293 goto_file_end, "Goto file end",
292 goto_file, "Goto files in selection",
294 goto_file, "Goto files in selection",
293 goto_file_hsplit, "Goto files in selection (hsplit)",
295 goto_file_hsplit, "Goto files in selection (hsplit)",
@@ -360,6 +362,7 @@ impl MappableCommand {
360 rotate_selection_contents_forward, "Rotate selection contents forward",
362 rotate_selection_contents_forward, "Rotate selection contents forward",
361 rotate_selection_contents_backward, "Rotate selections contents backward",
363 rotate_selection_contents_backward, "Rotate selections contents backward",
362 expand_selection, "Expand selection to parent syntax node",
364 expand_selection, "Expand selection to parent syntax node",
365+ shrink_selection, "Shrink selection to previously expanded syntax node",
363 jump_forward, "Jump forward on jumplist",
366 jump_forward, "Jump forward on jumplist",
364 jump_backward, "Jump backward on jumplist",
367 jump_backward, "Jump backward on jumplist",
365 save_selection, "Save the current selection to the jumplist",
368 save_selection, "Save the current selection to the jumplist",
@@ -396,7 +399,7 @@ impl MappableCommand {
396 increment, "Increment",
399 increment, "Increment",
397 decrement, "Decrement",
400 decrement, "Decrement",
398 record_macro, "Record macro",
401 record_macro, "Record macro",
399− play_macro, "Play macro",
402+ replay_macro, "Replay macro",
@@ -1280,16 +1283,23 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
1280 .max(view.offset.row + scrolloff)
1283 .max(view.offset.row + scrolloff)
1281 .min(last_line.saturating_sub(scrolloff));
1284 .min(last_line.saturating_sub(scrolloff));
1286+ // If cursor needs moving, replace primary selection
1287+ if line != cursor.row {
1283− let head = pos_at_coords(text, Position::new(line, cursor.col), true); // this func will properly truncate to line end
1288+ let head = pos_at_coords(text, Position::new(line, cursor.col), true); // this func will properly truncate to line end
1285− let anchor = if doc.mode == Mode::Select {
1290+ let anchor = if doc.mode == Mode::Select {
1286− range.anchor
1291+ range.anchor
1287− } else {
1292+ } else {
1291− // TODO: only manipulate main selection
1296+ // replace primary selection with an empty selection at cursor pos
1292− doc.set_selection(view.id, Selection::single(anchor, head));
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);
1295fn page_up(cx: &mut Context) {
1305fn page_up(cx: &mut Context) {
@@ -1543,7 +1553,7 @@ fn searcher(cx: &mut Context, direction: Direction) {
1543 let reg = cx.register.unwrap_or('/');
1553 let reg = cx.register.unwrap_or('/');
1544 let scrolloff = cx.editor.config.scrolloff;
1554 let scrolloff = cx.editor.config.scrolloff;
1546− let (_, doc) = current!(cx.editor);
1556+ let doc = doc!(cx.editor);
1548 // TODO: could probably share with select_on_matches?
1558 // TODO: could probably share with select_on_matches?
@@ -1630,7 +1640,7 @@ fn search_selection(cx: &mut Context) {
1630 let query = doc.selection(view.id).primary().fragment(contents);
1640 let query = doc.selection(view.id).primary().fragment(contents);
1631 let regex = regex::escape(&query);
1641 let regex = regex::escape(&query);
1632 cx.editor.registers.get_mut('/').push(regex);
1642 cx.editor.registers.get_mut('/').push(regex);
1633− let msg = format!("register '{}' set to '{}'", '\\', query);
1643+ let msg = format!("register '{}' set to '{}'", '/', query);
1634 cx.editor.set_status(msg);
1644 cx.editor.set_status(msg);
@@ -1904,7 +1914,21 @@ fn flip_selections(cx: &mut Context) {
1904 let selection = doc
1914 let selection = doc
1905 .selection(view.id)
1915 .selection(view.id)
1906 .clone()
1916 .clone()
1907− .transform(|range| Range::new(range.head, range.anchor));
1917+ .transform(|range| range.flip());
1918+ doc.set_selection(view.id, selection);
1921+fn ensure_selections_forward(cx: &mut Context) {
1922+ let (view, doc) = current!(cx.editor);
1924+ let selection = doc
1925+ .selection(view.id)
1927+ .transform(|r| match r.direction() {
1928+ Direction::Forward => r,
1929+ Direction::Backward => r.flip(),
1908 doc.set_selection(view.id, selection);
1932 doc.set_selection(view.id, selection);
@@ -1938,7 +1962,7 @@ fn append_mode(cx: &mut Context) {
1938 if !last_range.is_empty() && last_range.head == end {
1962 if !last_range.is_empty() && last_range.head == end {
1939 let transaction = Transaction::change(
1963 let transaction = Transaction::change(
1940 doc.text(),
1964 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(),
1943 doc.apply(&transaction, view.id);
1967 doc.apply(&transaction, view.id);
@@ -2030,7 +2054,7 @@ pub mod cmd {
2031 fn write_impl(cx: &mut compositor::Context, path: Option<&Cow<str>>) -> anyhow::Result<()> {
2055 fn write_impl(cx: &mut compositor::Context, path: Option<&Cow<str>>) -> anyhow::Result<()> {
2032 let jobs = &mut cx.jobs;
2056 let jobs = &mut cx.jobs;
2033− let (_, doc) = current!(cx.editor);
2057+ let doc = doc_mut!(cx.editor);
2035 if let Some(ref path) = path {
2059 if let Some(ref path) = path {
2036 doc.set_path(Some(path.as_ref().as_ref()))
2060 doc.set_path(Some(path.as_ref().as_ref()))
@@ -2083,8 +2107,7 @@ pub mod cmd {
2083 _args: &[Cow<str>],
2107 _args: &[Cow<str>],
2084 _event: PromptEvent,
2108 _event: PromptEvent,
2085 ) -> anyhow::Result<()> {
2109 ) -> anyhow::Result<()> {
2086− let (_, doc) = current!(cx.editor);
2110+ let doc = doc!(cx.editor);
2088 if let Some(format) = doc.format() {
2111 if let Some(format) = doc.format() {
2089 let callback =
2112 let callback =
2090 make_format_callback(doc.id(), doc.version(), Modified::LeaveModified, format);
2113 make_format_callback(doc.id(), doc.version(), Modified::LeaveModified, format);
@@ -2307,12 +2330,7 @@ pub mod cmd {
2307 write_all_impl(cx, args, event, true, true)
2330 write_all_impl(cx, args, event, true, true)
2310− fn quit_all_impl(
2333+ fn quit_all_impl(editor: &mut Editor, force: bool) -> anyhow::Result<()> {
2311− editor: &mut Editor,
2312− _args: &[Cow<str>],
2313− _event: PromptEvent,
2315− ) -> anyhow::Result<()> {
2316 if !force {
2334 if !force {
2317 buffers_remaining_impl(editor)?;
2335 buffers_remaining_impl(editor)?;
@@ -2328,18 +2346,18 @@ pub mod cmd {
2329 fn quit_all(
2347 fn quit_all(
2330 cx: &mut compositor::Context,
2348 cx: &mut compositor::Context,
2331− args: &[Cow<str>],
2349+ _args: &[Cow<str>],
2332− event: PromptEvent,
2350+ _event: PromptEvent,
2333 ) -> anyhow::Result<()> {
2351 ) -> anyhow::Result<()> {
2334− quit_all_impl(cx.editor, args, event, false)
2352+ quit_all_impl(cx.editor, false)
2337 fn force_quit_all(
2355 fn force_quit_all(
2338 cx: &mut compositor::Context,
2356 cx: &mut compositor::Context,
2339− args: &[Cow<str>],
2357+ _args: &[Cow<str>],
2340− event: PromptEvent,
2358+ _event: PromptEvent,
2341 ) -> anyhow::Result<()> {
2359 ) -> anyhow::Result<()> {
2342− quit_all_impl(cx.editor, args, event, true)
2360+ quit_all_impl(cx.editor, true)
2345 fn cquit(
2363 fn cquit(
@@ -2353,12 +2371,21 @@ pub mod cmd {
2353 .unwrap_or(1);
2371 .unwrap_or(1);
2354 cx.editor.exit_code = exit_code;
2372 cx.editor.exit_code = exit_code;
2356− let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect();
2374+ quit_all_impl(cx.editor, false)
2357− for view_id in views {
2358− cx.editor.close(view_id);
2361− Ok(())
2377+ fn force_cquit(
2378+ cx: &mut compositor::Context,
2380+ _event: PromptEvent,
2381+ ) -> anyhow::Result<()> {
2382+ let exit_code = args
2384+ .and_then(|code| code.parse::<i32>().ok())
2386+ cx.editor.exit_code = exit_code;
2388+ quit_all_impl(cx.editor, true)
2364 fn theme(
2391 fn theme(
@@ -2393,7 +2420,7 @@ pub mod cmd {
2393 args: &[Cow<str>],
2420 args: &[Cow<str>],
2394 _event: PromptEvent,
2421 _event: PromptEvent,
2395 ) -> anyhow::Result<()> {
2422 ) -> anyhow::Result<()> {
2396− let (_, doc) = current!(cx.editor);
2423+ let doc = doc!(cx.editor);
2397 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
2424 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
2398 let separator = args.first().unwrap_or(&default_sep);
2425 let separator = args.first().unwrap_or(&default_sep);
2399 yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Clipboard)
2426 yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Clipboard)
@@ -2412,7 +2439,7 @@ pub mod cmd {
2412 args: &[Cow<str>],
2439 args: &[Cow<str>],
2413 _event: PromptEvent,
2440 _event: PromptEvent,
2414 ) -> anyhow::Result<()> {
2441 ) -> anyhow::Result<()> {
2415− let (_, doc) = current!(cx.editor);
2442+ let doc = doc!(cx.editor);
2416 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
2443 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
2417 let separator = args.first().unwrap_or(&default_sep);
2444 let separator = args.first().unwrap_or(&default_sep);
2418 yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Selection)
2445 yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Selection)
@@ -2539,7 +2566,7 @@ pub mod cmd {
2539 args: &[Cow<str>],
2566 args: &[Cow<str>],
2540 _event: PromptEvent,
2567 _event: PromptEvent,
2541 ) -> anyhow::Result<()> {
2568 ) -> anyhow::Result<()> {
2542− let (_, doc) = current!(cx.editor);
2569+ let doc = doc_mut!(cx.editor);
2543 if let Some(label) = args.first() {
2570 if let Some(label) = args.first() {
2544 doc.set_encoding(label)
2571 doc.set_encoding(label)
2545 } else {
2572 } else {
@@ -2637,6 +2664,86 @@ pub mod cmd {
2637 let (view, doc) = current!(cx.editor);
2664 let (view, doc) = current!(cx.editor);
2639 view.ensure_cursor_in_view(doc, line);
2666 view.ensure_cursor_in_view(doc, line);
2671+ cx: &mut compositor::Context,
2673+ _event: PromptEvent,
2674+ ) -> anyhow::Result<()> {
2675+ let runtime_config = &mut cx.editor.config;
2677+ if args.len() != 2 {
2678+ anyhow::bail!("Bad arguments. Usage: `:set key field`");
2681+ let (key, arg) = (&args[0].to_lowercase(), &args[1]);
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]),
2702+ cx: &mut compositor::Context,
2704+ _event: PromptEvent,
2705+ ) -> anyhow::Result<()> {
2706+ sort_impl(cx, args, false)
2710+ cx: &mut compositor::Context,
2712+ _event: PromptEvent,
2713+ ) -> anyhow::Result<()> {
2714+ sort_impl(cx, args, true)
2718+ cx: &mut compositor::Context,
2719+ _args: &[Cow<str>],
2721+ ) -> anyhow::Result<()> {
2722+ let (view, doc) = current!(cx.editor);
2723+ let text = doc.text().slice(..);
2725+ let selection = doc.selection(view.id);
2727+ let mut fragments: Vec<_> = selection
2729+ .map(|fragment| Tendril::from_slice(&fragment))
2732+ fragments.sort_by(match reverse {
2733+ true => |a: &Tendril, b: &Tendril| b.cmp(a),
2734+ false => |a: &Tendril, b: &Tendril| a.cmp(b),
2737+ let transaction = Transaction::change(
2742+ .map(|(s, fragment)| (s.from(), s.to(), Some(fragment))),
2745+ doc.apply(&transaction, view.id);
2746+ doc.append_changes_to_history(view.id);
@@ -2664,18 +2771,18 @@ pub mod cmd {
2664 completer: Some(completers::filename),
2771 completer: Some(completers::filename),
2666 TypableCommand {
2773 TypableCommand {
2667− name: "buffer-close",
2774+ name: "buffer-close",
2668− aliases: &["bc", "bclose"],
2775+ aliases: &["bc", "bclose"],
2669− doc: "Close the current buffer.",
2776+ doc: "Close the current buffer.",
2670− fun: buffer_close,
2777+ fun: buffer_close,
2671− completer: None, // FIXME: buffer completer
2778+ completer: None, // FIXME: buffer completer
2673 TypableCommand {
2780 TypableCommand {
2674− name: "buffer-close!",
2781+ name: "buffer-close!",
2675− aliases: &["bc!", "bclose!"],
2782+ aliases: &["bc!", "bclose!"],
2676− doc: "Close the current buffer forcefully (ignoring unsaved changes).",
2783+ doc: "Close the current buffer forcefully (ignoring unsaved changes).",
2677− fun: force_buffer_close,
2784+ fun: force_buffer_close,
2678− completer: None, // FIXME: buffer completer
2785+ completer: None, // FIXME: buffer completer
2680 TypableCommand {
2787 TypableCommand {
2681 name: "write",
2788 name: "write",
@@ -2782,6 +2889,13 @@ pub mod cmd {
2782 fun: cquit,
2889 fun: cquit,
2783 completer: None,
2890 completer: None,
2895+ doc: "Quit with exit code (default 1) forcefully (ignoring unsaved changes). Accepts an optional integer exit code (:cq! 2).",
2785 TypableCommand {
2899 TypableCommand {
2786 name: "theme",
2900 name: "theme",
2787 aliases: &[],
2901 aliases: &[],
@@ -2928,7 +3042,28 @@ pub mod cmd {
2928 doc: "Go to line number.",
3042 doc: "Go to line number.",
2929 fun: goto_line_number,
3043 fun: goto_line_number,
2930 completer: None,
3044 completer: None,
3047+ name: "set-option",
3049+ doc: "Set a config option at runtime",
3051+ completer: Some(completers::setting),
3056+ doc: "Sort ranges in selection.",
3063+ doc: "Sort ranges in selection in reverse order.",
2934 pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
3069 pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
@@ -2948,17 +3083,28 @@ fn command_mode(cx: &mut Context) {
2948 ":".into(),
3083 ":".into(),
2949 Some(':'),
3084 Some(':'),
2950 |input: &str| {
3085 |input: &str| {
3086+ static FUZZY_MATCHER: Lazy<fuzzy_matcher::skim::SkimMatcherV2> =
3087+ Lazy::new(fuzzy_matcher::skim::SkimMatcherV2::default);
2951 // we use .this over split_whitespace() because we care about empty segments
3089 // we use .this over split_whitespace() because we care about empty segments
2952 let parts = input.split(' ').collect::<Vec<&str>>();
3090 let parts = input.split(' ').collect::<Vec<&str>>();
2954 // simple heuristic: if there's no just one part, complete command name.
3092 // simple heuristic: if there's no just one part, complete command name.
2955 // if there's a space, per command completion kicks in.
3093 // if there's a space, per command completion kicks in.
2956 if parts.len() <= 1 {
3094 if parts.len() <= 1 {
2958− cmd::TYPABLE_COMMAND_LIST
3095+ let mut matches: Vec<_> = cmd::TYPABLE_COMMAND_LIST
3097+ .filter_map(|command| {
2960− .filter(|command| command.name.contains(input))
3099+ .fuzzy_match(command.name, input)
2961− .map(|command| (end.clone(), Cow::Borrowed(command.name)))
3100+ .map(|score| (command.name, score))
3104+ matches.sort_unstable_by_key(|(_file, score)| std::cmp::Reverse(*score));
3107+ .map(|(name, _)| (0.., name.into()))
2962 .collect()
3108 .collect()
2963 } else {
3109 } else {
2964 let part = parts.last().unwrap();
3110 let part = parts.last().unwrap();
@@ -3002,7 +3148,16 @@ fn command_mode(cx: &mut Context) {
3003 // Handle typable commands
3149 // Handle typable commands
3004 if let Some(cmd) = cmd::TYPABLE_COMMAND_MAP.get(parts[0]) {
3150 if let Some(cmd) = cmd::TYPABLE_COMMAND_MAP.get(parts[0]) {
3151+ let args = if cfg!(unix) {
3005− let args = shellwords::shellwords(input);
3152+ shellwords::shellwords(input)
3154+ // Windows doesn't support POSIX, so fallback for now
3157+ .map(|part| part.into())
3158+ .collect::<Vec<_>>()
3006 if let Err(e) = (cmd.fun)(cx, &args[1..], event) {
3161 if let Err(e) = (cmd.fun)(cx, &args[1..], event) {
3007 cx.editor.set_error(format!("{}", e));
3162 cx.editor.set_error(format!("{}", e));
@@ -3026,7 +3181,8 @@ fn command_mode(cx: &mut Context) {
3028fn file_picker(cx: &mut Context) {
3183fn file_picker(cx: &mut Context) {
3184+ // We don't specify language markers, root will be the root of the current git repo
3029− let root = find_root(None).unwrap_or_else(|| PathBuf::from("./"));
3185+ let root = find_root(None, &[]).unwrap_or_else(|| PathBuf::from("./"));
3030 let picker = ui::file_picker(root, &cx.editor.config);
3186 let picker = ui::file_picker(root, &cx.editor.config);
3031 cx.push_layer(Box::new(picker));
3187 cx.push_layer(Box::new(picker));
@@ -3118,7 +3274,7 @@ fn symbol_picker(cx: &mut Context) {
3118 nested_to_flat(list, file, child);
3274 nested_to_flat(list, file, child);
3121− let (_, doc) = current!(cx.editor);
3277+ let doc = doc!(cx.editor);
3123 let language_server = match doc.language_server() {
3279 let language_server = match doc.language_server() {
3124 Some(language_server) => language_server,
3280 Some(language_server) => language_server,
@@ -3139,7 +3295,7 @@ fn symbol_picker(cx: &mut Context) {
3139 let symbols = match symbols {
3295 let symbols = match symbols {
3140 lsp::DocumentSymbolResponse::Flat(symbols) => symbols,
3296 lsp::DocumentSymbolResponse::Flat(symbols) => symbols,
3141 lsp::DocumentSymbolResponse::Nested(symbols) => {
3297 lsp::DocumentSymbolResponse::Nested(symbols) => {
3142− let (_view, doc) = current!(editor);
3298+ let doc = doc!(editor);
3143 let mut flat_symbols = Vec::new();
3299 let mut flat_symbols = Vec::new();
3144 for symbol in symbols {
3300 for symbol in symbols {
3145 nested_to_flat(&mut flat_symbols, &doc.identifier(), symbol)
3301 nested_to_flat(&mut flat_symbols, &doc.identifier(), symbol)
@@ -3181,17 +3337,15 @@ fn symbol_picker(cx: &mut Context) {
3183fn workspace_symbol_picker(cx: &mut Context) {
3339fn workspace_symbol_picker(cx: &mut Context) {
3184− let (_, doc) = current!(cx.editor);
3340+ let doc = doc!(cx.editor);
3185−
3341+ let current_path = doc.path().cloned();
3186 let language_server = match doc.language_server() {
3342 let language_server = match doc.language_server() {
3187 Some(language_server) => language_server,
3343 Some(language_server) => language_server,
3188 None => return,
3344 None => return,
3190 let offset_encoding = language_server.offset_encoding();
3346 let offset_encoding = language_server.offset_encoding();
3192 let future = language_server.workspace_symbols("".to_string());
3347 let future = language_server.workspace_symbols("".to_string());
3194− let current_path = doc_mut!(cx.editor).path().cloned();
3195 cx.callback(
3349 cx.callback(
3197 move |_editor: &mut Editor,
3351 move |_editor: &mut Editor,
@@ -3277,12 +3431,19 @@ pub fn code_action(cx: &mut Context) {
3277 move |editor, code_action, _action| match code_action {
3431 move |editor, code_action, _action| match code_action {
3278 lsp::CodeActionOrCommand::Command(command) => {
3432 lsp::CodeActionOrCommand::Command(command) => {
3279 log::debug!("code action command: {:?}", command);
3433 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());
3282 lsp::CodeActionOrCommand::CodeAction(code_action) => {
3436 lsp::CodeActionOrCommand::CodeAction(code_action) => {
3283 log::debug!("code action: {:?}", code_action);
3437 log::debug!("code action: {:?}", code_action);
3284 if let Some(ref workspace_edit) = code_action.edit {
3438 if let Some(ref workspace_edit) = code_action.edit {
3439+ log::debug!("edit: {:?}", workspace_edit);
3285− apply_workspace_edit(editor, offset_encoding, workspace_edit)
3440+ apply_workspace_edit(editor, offset_encoding, workspace_edit);
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());
@@ -3293,6 +3454,25 @@ pub fn code_action(cx: &mut Context) {
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,
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;
3470+ if let Err(e) = res {
3471+ log::error!("execute LSP command: {}", e);
3296pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
3476pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
3297 use lsp::ResourceOp;
3477 use lsp::ResourceOp;
3298 use std::fs;
3478 use std::fs;
@@ -3346,7 +3526,7 @@ pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
3349−fn apply_workspace_edit(
3529+pub fn apply_workspace_edit(
3350 editor: &mut Editor,
3530 editor: &mut Editor,
3351 offset_encoding: OffsetEncoding,
3531 offset_encoding: OffsetEncoding,
3352 workspace_edit: &lsp::WorkspaceEdit,
3532 workspace_edit: &lsp::WorkspaceEdit,
@@ -3537,22 +3717,22 @@ fn open(cx: &mut Context, open: Open) {
3537 let mut offs = 0;
3717 let mut offs = 0;
3539 let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
3719 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);
3542− let line = match open {
3722+ let new_line = match open {
3543 // adjust position to the end of the line (next line - 1)
3723 // adjust position to the end of the line (next line - 1)
3544− Open::Below => line + 1,
3724+ Open::Below => cursor_line + 1,
3545 // adjust position to the end of the previous line (current line - 1)
3725 // adjust position to the end of the previous line (current line - 1)
3546− Open::Above => line,
3726+ Open::Above => cursor_line,
3549 // Index to insert newlines after, as well as the char width
3729 // Index to insert newlines after, as well as the char width
3550 // to use to compensate for those inserted newlines.
3730 // 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 {
3553 } else {
3733 } else {
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)),
3556 doc.line_ending.len_chars(),
3736 doc.line_ending.len_chars(),
@@ -3563,8 +3743,10 @@ fn open(cx: &mut Context, open: Open) {
3563 doc.syntax(),
3743 doc.syntax(),
3565 line_end_index,
3745 line_end_index,
3746+ new_line.saturating_sub(1),
3749+ .unwrap_or_else(|| indent::indent_level_for_line(text.line(cursor_line), doc.tab_width()));
3568 let indent = doc.indent_unit().repeat(indent_level);
3750 let indent = doc.indent_unit().repeat(indent_level);
3569 let indent_len = indent.len();
3751 let indent_len = indent.len();
3570 let mut text = String::with_capacity(1 + indent_len);
3752 let mut text = String::with_capacity(1 + indent_len);
@@ -3610,6 +3792,7 @@ fn normal_mode(cx: &mut Context) {
3611 doc.mode = Mode::Normal;
3793 doc.mode = Mode::Normal;
3795+ try_restore_indent(doc, view.id);
3613 doc.append_changes_to_history(view.id);
3796 doc.append_changes_to_history(view.id);
3615 // if leaving append mode, move cursor back by 1
3798 // if leaving append mode, move cursor back by 1
@@ -3627,6 +3810,40 @@ fn normal_mode(cx: &mut Context) {
3813+fn try_restore_indent(doc: &mut Document, view_id: ViewId) {
3814+ use helix_core::chars::char_is_whitespace;
3815+ use helix_core::Operation;
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(_)] =
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
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));
3836+ if inserted_a_new_blank_line(doc_changes, pos, line_end_pos) {
3837+ // Removes tailing whitespaces.
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)
3843+ doc.apply(&transaction, view_id);
3630// Store a jump on the jumplist.
3847// Store a jump on the jumplist.
3631fn push_jump(editor: &mut Editor) {
3848fn push_jump(editor: &mut Editor) {
3632 let (view, doc) = current!(editor);
3849 let (view, doc) = current!(editor);
@@ -3994,27 +4211,21 @@ fn goto_pos(editor: &mut Editor, pos: usize) {
3996fn goto_first_diag(cx: &mut Context) {
4213fn goto_first_diag(cx: &mut Context) {
3997− let editor = &mut cx.editor;
4214+ let doc = doc!(cx.editor);
3998− let (_, doc) = current!(editor);
4000 let pos = match doc.diagnostics().first() {
4215 let pos = match doc.diagnostics().first() {
4001 Some(diag) => diag.range.start,
4216 Some(diag) => diag.range.start,
4002 None => return,
4217 None => return,
4005− goto_pos(editor, pos);
4219+ goto_pos(cx.editor, pos);
4008fn goto_last_diag(cx: &mut Context) {
4222fn goto_last_diag(cx: &mut Context) {
4009− let editor = &mut cx.editor;
4223+ let doc = doc!(cx.editor);
4010− let (_, doc) = current!(editor);
4012 let pos = match doc.diagnostics().last() {
4224 let pos = match doc.diagnostics().last() {
4013 Some(diag) => diag.range.start,
4225 Some(diag) => diag.range.start,
4014 None => return,
4226 None => return,
4017− goto_pos(editor, pos);
4228+ goto_pos(cx.editor, pos);
4020fn goto_next_diag(cx: &mut Context) {
4231fn goto_next_diag(cx: &mut Context) {
@@ -4270,48 +4481,48 @@ pub mod insert {
4271 let curr = contents.get_char(pos).unwrap_or(' ');
4482 let curr = contents.get_char(pos).unwrap_or(' ');
4273− // TODO: offset range.head by 1? when calculating?
4484+ let current_line = text.char_to_line(pos);
4274 let indent_level = indent::suggested_indent_for_pos(
4485 let indent_level = indent::suggested_indent_for_pos(
4275 doc.language_config(),
4486 doc.language_config(),
4276 doc.syntax(),
4487 doc.syntax(),
4278− pos.saturating_sub(1),
4489+ pos,
4281− let indent = doc.indent_unit().repeat(indent_level);
4493+ .unwrap_or_else(|| {
4282− let mut text = String::with_capacity(1 + indent.len());
4494+ indent::indent_level_for_line(text.line(current_line), doc.tab_width())
4283− text.push_str(doc.line_ending.as_str());
4495+ });
4284− text.push_str(&indent);
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);
4286− let head = pos + offs + text.chars().count();
4505+ let new_head_pos = pos + offs + text.chars().count();
4506+ text.push_str(doc.line_ending.as_str());
4507+ text.push_str(&indent);
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()
4288 // TODO: range replace or extend
4516 // TODO: range replace or extend
4289 // range.replace(|range| range.is_empty(), head); -> fn extend if cond true, new head pos
4517 // range.replace(|range| range.is_empty(), head); -> fn extend if cond true, new head pos
4290 // can be used with cx.mode to do replace or extend on most changes
4518 // can be used with cx.mode to do replace or extend on most changes
4291− ranges.push(Range::new(
4519+ ranges.push(Range::new(new_head_pos, new_head_pos));
4292− if range.is_empty() {
4295− range.anchor + offs
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);
4308 offs += text.chars().count();
4520 offs += text.chars().count();
4310 (pos, pos, Some(text.into()))
4522 (pos, pos, Some(text.into()))
4313 transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
4525 transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
4316 doc.apply(&transaction, view.id);
4527 doc.apply(&transaction, view.id);
@@ -5079,7 +5290,7 @@ pub fn completion(cx: &mut Context) {
5079 move |editor: &mut Editor,
5290 move |editor: &mut Editor,
5080 compositor: &mut Compositor,
5291 compositor: &mut Compositor,
5081 response: Option<lsp::CompletionResponse>| {
5292 response: Option<lsp::CompletionResponse>| {
5082− let (_, doc) = current!(editor);
5293+ let doc = doc!(editor);
5083 if doc.mode() != Mode::Insert {
5294 if doc.mode() != Mode::Insert {
5084 // we're not in insert mode anymore
5295 // we're not in insert mode anymore
@@ -5257,6 +5468,7 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
5257 doc.apply(&transaction, view.id);
5468 doc.apply(&transaction, view.id);
5258 doc.append_changes_to_history(view.id);
5469 doc.append_changes_to_history(view.id);
5260fn rotate_selection_contents_forward(cx: &mut Context) {
5472fn rotate_selection_contents_forward(cx: &mut Context) {
5261 rotate_selection_contents(cx, Direction::Forward)
5473 rotate_selection_contents(cx, Direction::Forward)
@@ -5272,7 +5484,39 @@ fn expand_selection(cx: &mut Context) {
5273 if let Some(syntax) = doc.syntax() {
5485 if let Some(syntax) = doc.syntax() {
5274 let text = doc.text().slice(..);
5486 let text = doc.text().slice(..);
5488+ let current_selection = doc.selection(view.id);
5490+ // save current selection so it can be restored using shrink_selection
5491+ view.object_selections.push(current_selection.clone());
5275− let selection = object::expand_selection(syntax, text, doc.selection(view.id));
5493+ let selection = object::expand_selection(syntax, text, current_selection);
5494+ doc.set_selection(view.id, selection);
5498+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
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);
5512+ // clear existing selection as they can't be shrinked to anyway
5513+ view.object_selections.clear();
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);
5276 doc.set_selection(view.id, selection);
5520 doc.set_selection(view.id, selection);
@@ -5920,42 +6164,42 @@ fn record_macro(cx: &mut Context) {
5920 keys.pop();
6164 keys.pop();
5921 let s = keys
6165 let s = keys
5922 .into_iter()
6166 .into_iter()
5923− .map(|key| format!("{}", key))
6167+ .map(|key| {
6168+ let s = key.to_string();
6169+ if s.chars().count() == 1 {
5924− .collect::<Vec<_>>()
6175+ .collect::<String>();
5926 cx.editor.registers.get_mut(reg).write(vec![s]);
6176 cx.editor.registers.get_mut(reg).write(vec![s]);
5927 cx.editor
6177 cx.editor
5928− .set_status(format!("Recorded to register {}", reg));
6178+ .set_status(format!("Recorded to register [{}]", reg));
5929 } else {
6179 } else {
5930 let reg = cx.register.take().unwrap_or('@');
6180 let reg = cx.register.take().unwrap_or('@');
5931 cx.editor.macro_recording = Some((reg, Vec::new()));
6181 cx.editor.macro_recording = Some((reg, Vec::new()));
5932 cx.editor
6182 cx.editor
5933− .set_status(format!("Recording to register {}", reg));
6183+ .set_status(format!("Recording to register [{}]", reg));
5937−fn play_macro(cx: &mut Context) {
6187+fn replay_macro(cx: &mut Context) {
5938 let reg = cx.register.unwrap_or('@');
6188 let reg = cx.register.unwrap_or('@');
5939− let keys = match cx
6189+ let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) {
5940− .editor
6190+ match helix_view::input::parse_macro(keys_str) {
5943− .and_then(|reg| reg.read().get(0))
5944− .context("Register empty")
5946− s.split_whitespace()
5947− .map(str::parse::<KeyEvent>)
5948− .collect::<Result<Vec<_>, _>>()
5949− .context("Failed to parse macro")
5951− Ok(keys) => keys,
6191+ Ok(keys) => keys,
5952− Err(e) => {
6192+ Err(err) => {
5953− cx.editor.set_error(format!("{}", e));
6193+ cx.editor.set_error(format!("Invalid macro: {}", err));
5954− return;
6194+ return;
6198+ cx.editor.set_error(format!("Register [{}] empty", reg));
5957− let count = cx.count();
6202+ let count = cx.count();
5959 cx.callback = Some(Box::new(
6203 cx.callback = Some(Box::new(
5960 move |compositor: &mut Compositor, cx: &mut compositor::Context| {
6204 move |compositor: &mut Compositor, cx: &mut compositor::Context| {
5961 for _ in 0..count {
6205 for _ in 0..count {