@@ -12,7 +12,7 @@ pub use typed::*;
1212use helix_core::{
1313 char_idx_at_visual_offset, comment,
1414 doc_formatter::TextFormat,
15− encoding, find_first_non_whitespace_char, find_root, graphemes,
15+ encoding, find_first_non_whitespace_char, find_workspace, graphemes,
1616 history::UndoKind,
1717 increment, indent,
1818 indent::IndentStyle,
@@ -23,17 +23,17 @@ use helix_core::{
2323 regex::{self, Regex, RegexBuilder},
2424 search::{self, CharMatcher},
2525 selection, shellwords, surround,
26+ syntax::LanguageServerFeature,
2627 text_annotations::TextAnnotations,
2728 textobject,
2829 tree_sitter::Node,
2930 unicode::width::UnicodeWidthChar,
30− visual_offset_from_block, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice,
31− Selection, SmallVec, Tendril, Transaction,
31+ visual_offset_from_block, Deletion, LineEnding, Position, Range, Rope, RopeGraphemes,
32+ RopeReader, RopeSlice, Selection, SmallVec, Tendril, Transaction,
3233};
3334use helix_view::{
34− clipboard::ClipboardType,
3535 document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
36− editor::{Action, Motion},
36+ editor::{Action, CompleteAction},
3737 info::Info,
3838 input::KeyEvent,
3939 keyboard::KeyCode,
@@ -43,7 +43,6 @@ use helix_view::{
4343};
4444
4545use anyhow::{anyhow, bail, ensure, Context as _};
46−use fuzzy_matcher::FuzzyMatcher;
4746use insert::*;
4847use movement::Movement;
4948
@@ -54,15 +53,20 @@ use crate::{
5453 job::Callback,
5554 keymap::ReverseKeymap,
5655 ui::{
57− self, editor::InsertEvent, overlay::overlayed, FilePicker, Picker, Popup, Prompt,
58− PromptEvent,
56+ self, editor::InsertEvent, lsp::SignatureHelp, overlay::overlaid, CompletionItem, Picker,
57+ Popup, Prompt, PromptEvent,
5958 },
6059};
6160
6261use crate::job::{self, Jobs};
63−use futures_util::StreamExt;
64−use std::{collections::HashMap, fmt, future::Future};
65−use std::{collections::HashSet, num::NonZeroUsize};
62+use futures_util::{stream::FuturesUnordered, TryStreamExt};
63+use std::{
64+ collections::{HashMap, HashSet},
65+ fmt,
66+ future::Future,
67+ io::Read,
68+ num::NonZeroUsize,
69+};
6670
6771use std::{
6872 borrow::Cow,
@@ -71,11 +75,11 @@ use std::{
7175
7276use once_cell::sync::Lazy;
7377use serde::de::{self, Deserialize, Deserializer};
78+use url::Url;
7479
7580use grep_regex::RegexMatcherBuilder;
7681use grep_searcher::{sinks, BinaryDetection, SearcherBuilder};
7782use ignore::{DirEntry, WalkBuilder, WalkState};
78−use tokio_stream::wrappers::UnboundedReceiverStream;
7983
8084pub type OnKeyCallback = Box<dyn FnOnce(&mut Context, KeyEvent)>;
8185
@@ -97,6 +101,13 @@ impl<'a> Context<'a> {
97101 }));
98102 }
99103
104+ /// Call `replace_or_push` on the Compositor
105+ pub fn replace_or_push_layer<T: Component>(&mut self, id: &'static str, component: T) {
106+ self.callback = Some(Box::new(move |compositor: &mut Compositor, _| {
107+ compositor.replace_or_push(id, component);
108+ }));
109+ }
110+
100111 #[inline]
101112 pub fn on_next_key(
102113 &mut self,
@@ -239,6 +250,9 @@ impl MappableCommand {
239250 move_next_long_word_start, "Move to start of next long word",
240251 move_prev_long_word_start, "Move to start of previous long word",
241252 move_next_long_word_end, "Move to end of next long word",
253+ move_prev_long_word_end, "Move to end of previous long word",
254+ move_parent_node_end, "Move to end of the parent node",
255+ move_parent_node_start, "Move to beginning of the parent node",
242256 extend_next_word_start, "Extend to start of next word",
243257 extend_prev_word_start, "Extend to start of previous word",
244258 extend_next_word_end, "Extend to end of next word",
@@ -246,6 +260,9 @@ impl MappableCommand {
246260 extend_next_long_word_start, "Extend to start of next long word",
247261 extend_prev_long_word_start, "Extend to start of previous long word",
248262 extend_next_long_word_end, "Extend to end of next long word",
263+ extend_prev_long_word_end, "Extend to end of prev long word",
264+ extend_parent_node_end, "Extend to end of the parent node",
265+ extend_parent_node_start, "Extend to beginning of the parent node",
249266 find_till_char, "Move till next occurrence of char",
250267 find_next_char, "Move to next occurrence of char",
251268 extend_till_char, "Extend till next occurrence of char",
@@ -267,6 +284,7 @@ impl MappableCommand {
267284 select_regex, "Select all regex matches inside selections",
268285 split_selection, "Split selections on regex matches",
269286 split_selection_on_newline, "Split selection on newlines",
287+ merge_selections, "Merge selections",
270288 merge_consecutive_selections, "Merge consecutive selections",
271289 search, "Search for regex pattern",
272290 rsearch, "Reverse search for regex pattern",
@@ -319,7 +337,7 @@ impl MappableCommand {
319337 goto_implementation, "Goto implementation",
320338 goto_file_start, "Goto line number <n> else file start",
321339 goto_file_end, "Goto file end",
322− goto_file, "Goto files in selection",
340+ goto_file, "Goto files/URLs in selection",
323341 goto_file_hsplit, "Goto files in selection (hsplit)",
324342 goto_file_vsplit, "Goto files in selection (vsplit)",
325343 goto_reference, "Goto references",
@@ -347,9 +365,11 @@ impl MappableCommand {
347365 goto_first_nonwhitespace, "Goto first non-blank in line",
348366 trim_selections, "Trim whitespace from selections",
349367 extend_to_line_start, "Extend to line start",
368+ extend_to_first_nonwhitespace, "Extend to first non-blank in line",
350369 extend_to_line_end, "Extend to line end",
351370 extend_to_line_end_newline, "Extend to line end",
352371 signature_help, "Show signature help",
372+ smart_tab, "Insert tab if all cursors have all whitespace to their left; otherwise, run a separate command.",
353373 insert_tab, "Insert tab char",
354374 insert_newline, "Insert newline char",
355375 delete_char_backward, "Delete previous char",
@@ -364,6 +384,9 @@ impl MappableCommand {
364384 later, "Move forward in history",
365385 commit_undo_checkpoint, "Commit changes to new checkpoint",
366386 yank, "Yank selection",
387+ yank_to_clipboard, "Yank selections to clipboard",
388+ yank_to_primary_clipboard, "Yank selections to primary clipboard",
389+ yank_joined, "Join and yank selections",
367390 yank_joined_to_clipboard, "Join and yank selections to clipboard",
368391 yank_main_selection_to_clipboard, "Yank main selection to clipboard",
369392 yank_joined_to_primary_clipboard, "Join and yank selections to primary clipboard",
@@ -394,6 +417,7 @@ impl MappableCommand {
394417 rotate_selections_backward, "Rotate selections backward",
395418 rotate_selection_contents_forward, "Rotate selection contents forward",
396419 rotate_selection_contents_backward, "Rotate selections contents backward",
420+ reverse_selection_contents, "Reverse selections contents",
397421 expand_selection, "Expand selection to parent syntax node",
398422 shrink_selection, "Shrink selection to previously expanded syntax node",
399423 select_next_sibling, "Select next sibling in syntax tree",
@@ -501,7 +525,7 @@ impl std::str::FromStr for MappableCommand {
501525
502526 fn from_str(s: &str) -> Result<Self, Self::Err> {
503527 if let Some(suffix) = s.strip_prefix(':') {
504− let mut typable_command = suffix.split(' ').into_iter().map(|arg| arg.trim());
528+ let mut typable_command = suffix.split(' ').map(|arg| arg.trim());
505529 let name = typable_command
506530 .next()
507531 .ok_or_else(|| anyhow!("Expected typable command name"))?;
@@ -787,58 +811,72 @@ fn extend_to_line_start(cx: &mut Context) {
787811}
788812
789813fn kill_to_line_start(cx: &mut Context) {
790− let (view, doc) = current!(cx.editor);
791− let text = doc.text().slice(..);
792−
793− let selection = doc.selection(view.id).clone().transform(|range| {
794− let line = range.cursor_line(text);
795− let first_char = text.line_to_char(line);
796− let anchor = range.cursor(text);
797− let head = if anchor == first_char && line != 0 {
798− // select until previous line
799− line_end_char_index(&text, line - 1)
800− } else if let Some(pos) = find_first_non_whitespace_char(text.line(line)) {
801− if first_char + pos < anchor {
802− // select until first non-blank in line if cursor is after it
803− first_char + pos
814+ delete_by_selection_insert_mode(
815+ cx,
816+ move |text, range| {
817+ let line = range.cursor_line(text);
818+ let first_char = text.line_to_char(line);
819+ let anchor = range.cursor(text);
820+ let head = if anchor == first_char && line != 0 {
821+ // select until previous line
822+ line_end_char_index(&text, line - 1)
823+ } else if let Some(pos) = find_first_non_whitespace_char(text.line(line)) {
824+ if first_char + pos < anchor {
825+ // select until first non-blank in line if cursor is after it
826+ first_char + pos
827+ } else {
828+ // select until start of line
829+ first_char
830+ }
804831 } else {
805832 // select until start of line
806833 first_char
807− }
808− } else {
809− // select until start of line
810− first_char
811− };
812− Range::new(head, anchor)
813− });
814− delete_selection_insert_mode(doc, view, &selection);
815−
816− lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
834+ };
835+ (head, anchor)
836+ },
837+ Direction::Backward,
838+ );
817839}
818840
819841fn kill_to_line_end(cx: &mut Context) {
820− let (view, doc) = current!(cx.editor);
821− let text = doc.text().slice(..);
842+ delete_by_selection_insert_mode(
843+ cx,
844+ |text, range| {
845+ let line = range.cursor_line(text);
846+ let line_end_pos = line_end_char_index(&text, line);
847+ let pos = range.cursor(text);
822848
823− let selection = doc.selection(view.id).clone().transform(|range| {
824− let line = range.cursor_line(text);
825− let line_end_pos = line_end_char_index(&text, line);
826− let pos = range.cursor(text);
849+ // if the cursor is on the newline char delete that
850+ if pos == line_end_pos {
851+ (pos, text.line_to_char(line + 1))
852+ } else {
853+ (pos, line_end_pos)
854+ }
855+ },
856+ Direction::Forward,
857+ );
858+}
827859
828− let mut new_range = range.put_cursor(text, line_end_pos, true);
829− // don't want to remove the line separator itself if the cursor doesn't reach the end of line.
830− if pos != line_end_pos {
831− new_range.head = line_end_pos;
832− }
833− new_range
834− });
835− delete_selection_insert_mode(doc, view, &selection);
860+fn goto_first_nonwhitespace(cx: &mut Context) {
861+ let (view, doc) = current!(cx.editor);
836862
837− lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
863+ goto_first_nonwhitespace_impl(
864+ view,
865+ doc,
866+ if cx.editor.mode == Mode::Select {
867+ Movement::Extend
868+ } else {
869+ Movement::Move
870+ },
871+ )
838872}
839873
840−fn goto_first_nonwhitespace(cx: &mut Context) {
874+fn extend_to_first_nonwhitespace(cx: &mut Context) {
841875 let (view, doc) = current!(cx.editor);
876+ goto_first_nonwhitespace_impl(view, doc, Movement::Extend)
877+}
878+
879+fn goto_first_nonwhitespace_impl(view: &mut View, doc: &mut Document, movement: Movement) {
842880 let text = doc.text().slice(..);
843881
844882 let selection = doc.selection(view.id).clone().transform(|range| {
@@ -846,7 +884,7 @@ fn goto_first_nonwhitespace(cx: &mut Context) {
846884
847885 if let Some(pos) = find_first_non_whitespace_char(text.line(line)) {
848886 let pos = pos + text.line_to_char(line);
849− range.put_cursor(text, pos, cx.editor.mode == Mode::Select)
887+ range.put_cursor(text, pos, movement == Movement::Extend)
850888 } else {
851889 range
852890 }
@@ -952,6 +990,7 @@ fn align_selections(cx: &mut Context) {
952990
953991 let transaction = Transaction::change(doc.text(), changes.into_iter());
954992 doc.apply(&transaction, view.id);
993+ exit_select_mode(cx);
955994}
956995
957996fn goto_window(cx: &mut Context, align: Align) {
@@ -1043,6 +1082,10 @@ fn move_prev_long_word_start(cx: &mut Context) {
10431082 move_word_impl(cx, movement::move_prev_long_word_start)
10441083}
10451084
1085+fn move_prev_long_word_end(cx: &mut Context) {
1086+ move_word_impl(cx, movement::move_prev_long_word_end)
1087+}
1088+
10461089fn move_next_long_word_end(cx: &mut Context) {
10471090 move_word_impl(cx, movement::move_next_long_word_end)
10481091}
@@ -1067,8 +1110,7 @@ where
10671110 .transform(|range| move_fn(text, range, count, behavior));
10681111 doc.set_selection(view.id, selection);
10691112 };
1070− motion(cx.editor);
1071− cx.editor.last_motion = Some(Motion(Box::new(motion)));
1113+ cx.editor.apply_motion(motion)
10721114}
10731115
10741116fn goto_prev_paragraph(cx: &mut Context) {
@@ -1123,6 +1165,10 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
11231165 let (view, doc) = current_ref!(cx.editor);
11241166 let text = doc.text();
11251167 let selections = doc.selection(view.id);
1168+ let rel_path = doc
1169+ .relative_path()
1170+ .map(|path| path.parent().unwrap().to_path_buf())
1171+ .unwrap_or_default();
11261172 let mut paths: Vec<_> = selections
11271173 .iter()
11281174 .map(|r| text.slice(r.from()..r.to()).to_string())
@@ -1150,16 +1196,80 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
11501196 .to_string(),
11511197 );
11521198 }
1199+
11531200 for sel in paths {
11541201 let p = sel.trim();
1155− if !p.is_empty() {
1156− if let Err(e) = cx.editor.open(&PathBuf::from(p), action) {
1202+ if p.is_empty() {
1203+ continue;
1204+ }
1205+
1206+ if let Ok(url) = Url::parse(p) {
1207+ return open_url(cx, url, action);
1208+ }
1209+
1210+ let path = &rel_path.join(p);
1211+ if path.is_dir() {
1212+ let picker = ui::file_picker(path.into(), &cx.editor.config());
1213+ cx.push_layer(Box::new(overlaid(picker)));
1214+ } else if let Err(e) = cx.editor.open(path, action) {
1215+ cx.editor.set_error(format!("Open file failed: {:?}", e));
1216+ }
1217+ }
1218+}
1219+
1220+/// Opens the given url. If the URL points to a valid textual file it is open in helix.
1221+// Otherwise, the file is open using external program.
1222+fn open_url(cx: &mut Context, url: Url, action: Action) {
1223+ let doc = doc!(cx.editor);
1224+ let rel_path = doc
1225+ .relative_path()
1226+ .map(|path| path.parent().unwrap().to_path_buf())
1227+ .unwrap_or_default();
1228+
1229+ if url.scheme() != "file" {
1230+ return open_external_url(cx, url);
1231+ }
1232+
1233+ let content_type = std::fs::File::open(url.path()).and_then(|file| {
1234+ // Read up to 1kb to detect the content type
1235+ let mut read_buffer = Vec::new();
1236+ let n = file.take(1024).read_to_end(&mut read_buffer)?;
1237+ Ok(content_inspector::inspect(&read_buffer[..n]))
1238+ });
1239+
1240+ // we attempt to open binary files - files that can't be open in helix - using external
1241+ // program as well, e.g. pdf files or images
1242+ match content_type {
1243+ Ok(content_inspector::ContentType::BINARY) => open_external_url(cx, url),
1244+ Ok(_) | Err(_) => {
1245+ let path = &rel_path.join(url.path());
1246+ if path.is_dir() {
1247+ let picker = ui::file_picker(path.into(), &cx.editor.config());
1248+ cx.push_layer(Box::new(overlaid(picker)));
1249+ } else if let Err(e) = cx.editor.open(path, action) {
11571250 cx.editor.set_error(format!("Open file failed: {:?}", e));
11581251 }
11591252 }
11601253 }
11611254}
11621255
1256+/// Opens URL in external program.
1257+fn open_external_url(cx: &mut Context, url: Url) {
1258+ let commands = open::commands(url.as_str());
1259+ cx.jobs.callback(async {
1260+ for cmd in commands {
1261+ let mut command = tokio::process::Command::new(cmd.get_program());
1262+ command.args(cmd.get_args());
1263+ if command.output().await.is_ok() {
1264+ return Ok(job::Callback::Editor(Box::new(|_| {})));
1265+ }
1266+ }
1267+ Ok(job::Callback::Editor(Box::new(move |editor| {
1268+ editor.set_error("Opening URL in external program failed")
1269+ })))
1270+ });
1271+}
1272+
11631273fn extend_word_impl<F>(cx: &mut Context, extend_fn: F)
11641274where
11651275 F: Fn(RopeSlice, Range, usize) -> Range,
@@ -1200,14 +1310,74 @@ fn extend_prev_long_word_start(cx: &mut Context) {
12001310 extend_word_impl(cx, movement::move_prev_long_word_start)
12011311}
12021312
1313+fn extend_prev_long_word_end(cx: &mut Context) {
1314+ extend_word_impl(cx, movement::move_prev_long_word_end)
1315+}
1316+
12031317fn extend_next_long_word_end(cx: &mut Context) {
12041318 extend_word_impl(cx, movement::move_next_long_word_end)
12051319}
12061320
1207−fn will_find_char<F>(cx: &mut Context, search_fn: F, inclusive: bool, extend: bool)
1208−where
1209− F: Fn(RopeSlice, char, usize, usize, bool) -> Option<usize> + 'static,
1210−{
1321+/// Separate branch to find_char designed only for `<ret>` char.
1322+//
1323+// This is necessary because the one document can have different line endings inside. And we
1324+// cannot predict what character to find when <ret> is pressed. On the current line it can be `lf`
1325+// but on the next line it can be `crlf`. That's why [`find_char_impl`] cannot be applied here.
1326+fn find_char_line_ending(
1327+ cx: &mut Context,
1328+ count: usize,
1329+ direction: Direction,
1330+ inclusive: bool,
1331+ extend: bool,
1332+) {
1333+ let (view, doc) = current!(cx.editor);
1334+ let text = doc.text().slice(..);
1335+
1336+ let selection = doc.selection(view.id).clone().transform(|range| {
1337+ let cursor = range.cursor(text);
1338+ let cursor_line = range.cursor_line(text);
1339+
1340+ // Finding the line where we're going to find <ret>. Depends mostly on
1341+ // `count`, but also takes into account edge cases where we're already at the end
1342+ // of a line or the beginning of a line
1343+ let find_on_line = match direction {
1344+ Direction::Forward => {
1345+ let on_edge = line_end_char_index(&text, cursor_line) == cursor;
1346+ let line = cursor_line + count - 1 + (on_edge as usize);
1347+ if line >= text.len_lines() - 1 {
1348+ return range;
1349+ } else {
1350+ line
1351+ }
1352+ }
1353+ Direction::Backward => {
1354+ let on_edge = text.line_to_char(cursor_line) == cursor && !inclusive;
1355+ let line = cursor_line as isize - (count as isize - 1 + on_edge as isize);
1356+ if line <= 0 {
1357+ return range;
1358+ } else {
1359+ line as usize
1360+ }
1361+ }
1362+ };
1363+
1364+ let pos = match (direction, inclusive) {
1365+ (Direction::Forward, true) => line_end_char_index(&text, find_on_line),
1366+ (Direction::Forward, false) => line_end_char_index(&text, find_on_line) - 1,
1367+ (Direction::Backward, true) => line_end_char_index(&text, find_on_line - 1),
1368+ (Direction::Backward, false) => text.line_to_char(find_on_line),
1369+ };
1370+
1371+ if extend {
1372+ range.put_cursor(text, pos, true)
1373+ } else {
1374+ Range::point(range.cursor(text)).put_cursor(text, pos, true)
1375+ }
1376+ });
1377+ doc.set_selection(view.id, selection);
1378+}
1379+
1380+fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bool) {
12111381 // TODO: count is reset to 1 before next key so we move it into the closure here.
12121382 // Would be nice to carry over.
12131383 let count = cx.count();
@@ -1220,13 +1390,9 @@ where
12201390 KeyEvent {
12211391 code: KeyCode::Enter,
12221392 ..
1223− } =>
1224− // TODO: this isn't quite correct when CRLF is involved.
1225− // This hack will work in most cases, since documents don't
1226− // usually mix line endings. But we should fix it eventually
1227− // anyway.
1228− {
1229− doc!(cx.editor).line_ending.as_str().chars().next().unwrap()
1393+ } => {
1394+ find_char_line_ending(cx, count, direction, inclusive, extend);
1395+ return;
12301396 }
12311397
12321398 KeyEvent {
@@ -1239,11 +1405,18 @@ where
12391405 } => ch,
12401406 _ => return,
12411407 };
1408+ let motion = move |editor: &mut Editor| {
1409+ match direction {
1410+ Direction::Forward => {
1411+ find_char_impl(editor, &find_next_char_impl, inclusive, extend, ch, count)
1412+ }
1413+ Direction::Backward => {
1414+ find_char_impl(editor, &find_prev_char_impl, inclusive, extend, ch, count)
1415+ }
1416+ };
1417+ };
12421418
1243− find_char_impl(cx.editor, &search_fn, inclusive, extend, ch, count);
1244− cx.editor.last_motion = Some(Motion(Box::new(move |editor: &mut Editor| {
1245− find_char_impl(editor, &search_fn, inclusive, true, ch, 1);
1246− })));
1419+ cx.editor.apply_motion(motion);
12471420 })
12481421}
12491422
@@ -1322,46 +1495,39 @@ fn find_prev_char_impl(
13221495}
13231496
13241497fn find_till_char(cx: &mut Context) {
1325− will_find_char(cx, find_next_char_impl, false, false)
1498+ find_char(cx, Direction::Forward, false, false);
13261499}
13271500
13281501fn find_next_char(cx: &mut Context) {
1329− will_find_char(cx, find_next_char_impl, true, false)
1502+ find_char(cx, Direction::Forward, true, false)
13301503}
13311504
13321505fn extend_till_char(cx: &mut Context) {
1333− will_find_char(cx, find_next_char_impl, false, true)
1506+ find_char(cx, Direction::Forward, false, true)
13341507}
13351508
13361509fn extend_next_char(cx: &mut Context) {
1337− will_find_char(cx, find_next_char_impl, true, true)
1510+ find_char(cx, Direction::Forward, true, true)
13381511}
13391512
13401513fn till_prev_char(cx: &mut Context) {
1341− will_find_char(cx, find_prev_char_impl, false, false)
1514+ find_char(cx, Direction::Backward, false, false)
13421515}
13431516
13441517fn find_prev_char(cx: &mut Context) {
1345− will_find_char(cx, find_prev_char_impl, true, false)
1518+ find_char(cx, Direction::Backward, true, false)
13461519}
13471520
13481521fn extend_till_prev_char(cx: &mut Context) {
1349− will_find_char(cx, find_prev_char_impl, false, true)
1522+ find_char(cx, Direction::Backward, false, true)
13501523}
13511524
13521525fn extend_prev_char(cx: &mut Context) {
1353− will_find_char(cx, find_prev_char_impl, true, true)
1526+ find_char(cx, Direction::Backward, true, true)
13541527}
13551528
13561529fn repeat_last_motion(cx: &mut Context) {
1357− let count = cx.count();
1358− let last_motion = cx.editor.last_motion.take();
1359− if let Some(m) = &last_motion {
1360− for _ in 0..count {
1361− m.run(cx.editor);
1362− }
1363− cx.editor.last_motion = last_motion;
1364− }
1530+ cx.editor.repeat_last_motion(cx.count())
13651531}
13661532
13671533fn replace(cx: &mut Context) {
@@ -1428,6 +1594,7 @@ where
14281594 });
14291595
14301596 doc.apply(&transaction, view.id);
1597+ exit_select_mode(cx);
14311598}
14321599
14331600fn switch_case(cx: &mut Context) {
@@ -1470,7 +1637,7 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
14701637 let cursor = range.cursor(text);
14711638 let height = view.inner_height();
14721639
1473− let scrolloff = config.scrolloff.min(height / 2);
1640+ let scrolloff = config.scrolloff.min(height.saturating_sub(1) / 2);
14741641 let offset = match direction {
14751642 Forward => offset as isize,
14761643 Backward => -(offset as isize),
@@ -1489,18 +1656,19 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
14891656 &annotations,
14901657 );
14911658
1492− let head;
1659+ let mut head;
14931660 match direction {
14941661 Forward => {
1495− head = char_idx_at_visual_offset(
1662+ let off;
1663+ (head, off) = char_idx_at_visual_offset(
14961664 doc_text,
14971665 view.offset.anchor,
14981666 (view.offset.vertical_offset + scrolloff) as isize,
14991667 0,
15001668 &text_fmt,
15011669 &annotations,
1502− )
1503− .0;
1670+ );
1671+ head += (off != 0) as usize;
15041672 if head <= cursor {
15051673 return;
15061674 }
@@ -1509,7 +1677,7 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
15091677 head = char_idx_at_visual_offset(
15101678 doc_text,
15111679 view.offset.anchor,
1512− (view.offset.vertical_offset + height - scrolloff) as isize,
1680+ (view.offset.vertical_offset + height - scrolloff - 1) as isize,
15131681 0,
15141682 &text_fmt,
15151683 &annotations,
@@ -1560,7 +1728,7 @@ fn half_page_down(cx: &mut Context) {
15601728}
15611729
15621730#[allow(deprecated)]
1563−// currently uses the deprected `visual_coords_at_pos`/`pos_at_visual_coords` functions
1731+// currently uses the deprecated `visual_coords_at_pos`/`pos_at_visual_coords` functions
15641732// as this function ignores softwrapping (and virtual text) and instead only cares
15651733// about "text visual position"
15661734//
@@ -1668,8 +1836,8 @@ fn select_regex(cx: &mut Context) {
16681836 "select:".into(),
16691837 Some(reg),
16701838 ui::completers::none,
1671− move |editor, regex, event| {
1672− let (view, doc) = current!(editor);
1839+ move |cx, regex, event| {
1840+ let (view, doc) = current!(cx.editor);
16731841 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
16741842 return;
16751843 }
@@ -1690,8 +1858,8 @@ fn split_selection(cx: &mut Context) {
16901858 "split:".into(),
16911859 Some(reg),
16921860 ui::completers::none,
1693− move |editor, regex, event| {
1694− let (view, doc) = current!(editor);
1861+ move |cx, regex, event| {
1862+ let (view, doc) = current!(cx.editor);
16951863 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
16961864 return;
16971865 }
@@ -1713,6 +1881,12 @@ fn split_selection_on_newline(cx: &mut Context) {
17131881 doc.set_selection(view.id, selection);
17141882}
17151883
1884+fn merge_selections(cx: &mut Context) {
1885+ let (view, doc) = current!(cx.editor);
1886+ let selection = doc.selection(view.id).clone().merge_ranges();
1887+ doc.set_selection(view.id, selection);
1888+}
1889+
17161890fn merge_consecutive_selections(cx: &mut Context) {
17171891 let (view, doc) = current!(cx.editor);
17181892 let selection = doc.selection(view.id).clone().merge_consecutive_ranges();
@@ -1808,11 +1982,11 @@ fn search_impl(
18081982
18091983fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
18101984 let mut items = reg
1811− .and_then(|reg| cx.editor.registers.get(reg))
1812− .map_or(Vec::new(), |reg| reg.read().iter().take(200).collect());
1985+ .and_then(|reg| cx.editor.registers.read(reg, cx.editor))
1986+ .map_or(Vec::new(), |reg| reg.take(200).collect());
18131987 items.sort_unstable();
18141988 items.dedup();
1815− items.into_iter().cloned().collect()
1989+ items.into_iter().map(|value| value.to_string()).collect()
18161990}
18171991
18181992fn search(cx: &mut Context) {
@@ -1849,12 +2023,14 @@ fn searcher(cx: &mut Context, direction: Direction) {
18492023 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
18502024 .collect()
18512025 },
1852− move |editor, regex, event| {
1853− if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
2026+ move |cx, regex, event| {
2027+ if event == PromptEvent::Validate {
2028+ cx.editor.registers.last_search_register = reg;
2029+ } else if event != PromptEvent::Update {
18542030 return;
18552031 }
18562032 search_impl(
1857− editor,
2033+ cx.editor,
18582034 &contents,
18592035 ®ex,
18602036 Movement::Move,
@@ -1869,11 +2045,13 @@ fn searcher(cx: &mut Context, direction: Direction) {
18692045
18702046fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) {
18712047 let count = cx.count();
2048+ let register = cx
2049+ .register
2050+ .unwrap_or(cx.editor.registers.last_search_register);
18722051 let config = cx.editor.config();
18732052 let scrolloff = config.scrolloff;
1874− let (_, doc) = current!(cx.editor);
1875− let registers = &cx.editor.registers;
1876− if let Some(query) = registers.read('/').and_then(|query| query.last()) {
2053+ if let Some(query) = cx.editor.registers.first(register, cx.editor) {
2054+ let doc = doc!(cx.editor);
18772055 let contents = doc.text().slice(..).to_string();
18782056 let search_config = &config.search;
18792057 let case_insensitive = if search_config.smart_case {
@@ -1882,7 +2060,7 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
18822060 false
18832061 };
18842062 let wrap_around = search_config.wrap_around;
1885− if let Ok(regex) = RegexBuilder::new(query)
2063+ if let Ok(regex) = RegexBuilder::new(&query)
18862064 .case_insensitive(case_insensitive)
18872065 .multi_line(true)
18882066 .build()
@@ -1922,6 +2100,7 @@ fn extend_search_prev(cx: &mut Context) {
19222100}
19232101
19242102fn search_selection(cx: &mut Context) {
2103+ let register = cx.register.unwrap_or('/');
19252104 let (view, doc) = current!(cx.editor);
19262105 let contents = doc.text().slice(..);
19272106
@@ -1934,13 +2113,24 @@ fn search_selection(cx: &mut Context) {
19342113 .collect::<Vec<_>>()
19352114 .join("|");
19362115
1937− let msg = format!("register '{}' set to '{}'", '/', ®ex);
1938− cx.editor.registers.push('/', regex);
1939− cx.editor.set_status(msg);
2116+ let msg = format!("register '{}' set to '{}'", register, ®ex);
2117+ match cx.editor.registers.push(register, regex) {
2118+ Ok(_) => {
2119+ cx.editor.registers.last_search_register = register;
2120+ cx.editor.set_status(msg)
2121+ }
2122+ Err(err) => cx.editor.set_error(err.to_string()),
2123+ }
19402124}
19412125
19422126fn make_search_word_bounded(cx: &mut Context) {
1943− let regex = match cx.editor.registers.last('/') {
2127+ // Defaults to the active search register instead `/` to be more ergonomic assuming most people
2128+ // would use this command following `search_selection`. This avoids selecting the register
2129+ // twice.
2130+ let register = cx
2131+ .register
2132+ .unwrap_or(cx.editor.registers.last_search_register);
2133+ let regex = match cx.editor.registers.first(register, cx.editor) {
19442134 Some(regex) => regex,
19452135 None => return,
19462136 };
@@ -1958,14 +2148,19 @@ fn make_search_word_bounded(cx: &mut Context) {
19582148 if !start_anchored {
19592149 new_regex.push_str("\\b");
19602150 }
1961− new_regex.push_str(regex);
2151+ new_regex.push_str(®ex);
19622152 if !end_anchored {
19632153 new_regex.push_str("\\b");
19642154 }
19652155
1966− let msg = format!("register '{}' set to '{}'", '/', &new_regex);
1967− cx.editor.registers.push('/', new_regex);
1968− cx.editor.set_status(msg);
2156+ let msg = format!("register '{}' set to '{}'", register, &new_regex);
2157+ match cx.editor.registers.push(register, new_regex) {
2158+ Ok(_) => {
2159+ cx.editor.registers.last_search_register = register;
2160+ cx.editor.set_status(msg)
2161+ }
2162+ Err(err) => cx.editor.set_error(err.to_string()),
2163+ }
19692164}
19702165
19712166fn global_search(cx: &mut Context) {
@@ -2004,13 +2199,11 @@ fn global_search(cx: &mut Context) {
20042199 }
20052200 }
20062201
2007− let (all_matches_sx, all_matches_rx) = tokio::sync::mpsc::unbounded_channel::<FileResult>();
20082202 let config = cx.editor.config();
20092203 let smart_case = config.search.smart_case;
20102204 let file_picker_config = config.file_picker.clone();
20112205
20122206 let reg = cx.register.unwrap_or('/');
2013−
20142207 let completions = search_completions(cx, Some(reg));
20152208 ui::regex_prompt(
20162209 cx,
@@ -2023,43 +2216,67 @@ fn global_search(cx: &mut Context) {
20232216 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
20242217 .collect()
20252218 },
2026− move |_editor, regex, event| {
2219+ move |cx, regex, event| {
20272220 if event != PromptEvent::Validate {
20282221 return;
20292222 }
2223+ cx.editor.registers.last_search_register = reg;
2224+
2225+ let current_path = doc_mut!(cx.editor).path().cloned();
2226+ let documents: Vec<_> = cx
2227+ .editor
2228+ .documents()
2229+ .map(|doc| (doc.path().cloned(), doc.text().to_owned()))
2230+ .collect();
20302231
20312232 if let Ok(matcher) = RegexMatcherBuilder::new()
20322233 .case_smart(smart_case)
20332234 .build(regex.as_str())
20342235 {
2035− let searcher = SearcherBuilder::new()
2036− .binary_detection(BinaryDetection::quit(b'\x00'))
2037− .build();
2236+ let search_root = helix_loader::current_working_dir();
2237+ if !search_root.exists() {
2238+ cx.editor
2239+ .set_error("Current working directory does not exist");
2240+ return;
2241+ }
2242+
2243+ let (picker, injector) = Picker::stream(current_path);
20382244
2039− let search_root = std::env::current_dir()
2040− .expect("Global search error: Failed to get current dir");
20412245 let dedup_symlinks = file_picker_config.deduplicate_links;
20422246 let absolute_root = search_root
20432247 .canonicalize()
20442248 .unwrap_or_else(|_| search_root.clone());
2045−
2046− WalkBuilder::new(search_root)
2047− .hidden(file_picker_config.hidden)
2048− .parents(file_picker_config.parents)
2049− .ignore(file_picker_config.ignore)
2050− .follow_links(file_picker_config.follow_symlinks)
2051− .git_ignore(file_picker_config.git_ignore)
2052− .git_global(file_picker_config.git_global)
2053− .git_exclude(file_picker_config.git_exclude)
2054− .max_depth(file_picker_config.max_depth)
2055− .filter_entry(move |entry| {
2056− filter_picker_entry(entry, &absolute_root, dedup_symlinks)
2057− })
2058− .build_parallel()
2059− .run(|| {
2249+ let injector_ = injector.clone();
2250+
2251+ std::thread::spawn(move || {
2252+ let searcher = SearcherBuilder::new()
2253+ .binary_detection(BinaryDetection::quit(b'\x00'))
2254+ .build();
2255+
2256+ let mut walk_builder = WalkBuilder::new(search_root);
2257+
2258+ walk_builder
2259+ .hidden(file_picker_config.hidden)
2260+ .parents(file_picker_config.parents)
2261+ .ignore(file_picker_config.ignore)
2262+ .follow_links(file_picker_config.follow_symlinks)
2263+ .git_ignore(file_picker_config.git_ignore)
2264+ .git_global(file_picker_config.git_global)
2265+ .git_exclude(file_picker_config.git_exclude)
2266+ .max_depth(file_picker_config.max_depth)
2267+ .filter_entry(move |entry| {
2268+ filter_picker_entry(entry, &absolute_root, dedup_symlinks)
2269+ });
2270+
2271+ walk_builder
2272+ .add_custom_ignore_filename(helix_loader::config_dir().join("ignore"));
2273+ walk_builder.add_custom_ignore_filename(".helix/ignore");
2274+
2275+ walk_builder.build_parallel().run(|| {
20602276 let mut searcher = searcher.clone();
20612277 let matcher = matcher.clone();
2062− let all_matches_sx = all_matches_sx.clone();
2278+ let injector = injector_.clone();
2279+ let documents = &documents;
20632280 Box::new(move |entry: Result<DirEntry, ignore::Error>| -> WalkState {
20642281 let entry = match entry {
20652282 Ok(entry) => entry,
@@ -2072,17 +2289,39 @@ fn global_search(cx: &mut Context) {
20722289 _ => return WalkState::Continue,
20732290 };
20742291
2075− let result = searcher.search_path(
2076− &matcher,
2077− entry.path(),
2078− sinks::UTF8(|line_num, _| {
2079− all_matches_sx
2080− .send(FileResult::new(entry.path(), line_num as usize - 1))
2081− .unwrap();
2082−
2083− Ok(true)
2084− }),
2085− );
2292+ let mut stop = false;
2293+ let sink = sinks::UTF8(|line_num, _| {
2294+ stop = injector
2295+ .push(FileResult::new(entry.path(), line_num as usize - 1))
2296+ .is_err();
2297+
2298+ Ok(!stop)
2299+ });
2300+ let doc = documents.iter().find(|&(doc_path, _)| {
2301+ doc_path
2302+ .as_ref()
2303+ .map_or(false, |doc_path| doc_path == entry.path())
2304+ });
2305+
2306+ let result = if let Some((_, doc)) = doc {
2307+ // there is already a buffer for this file
2308+ // search the buffer instead of the file because it's faster
2309+ // and captures new edits without requiring a save
2310+ if searcher.multi_line_with_matcher(&matcher) {
2311+ // in this case a continous buffer is required
2312+ // convert the rope to a string
2313+ let text = doc.to_string();
2314+ searcher.search_slice(&matcher, text.as_bytes(), sink)
2315+ } else {
2316+ searcher.search_reader(
2317+ &matcher,
2318+ RopeReader::new(doc.slice(..)),
2319+ sink,
2320+ )
2321+ }
2322+ } else {
2323+ searcher.search_path(&matcher, entry.path(), sink)
2324+ };
20862325
20872326 if let Err(err) = result {
20882327 log::error!(
@@ -2091,67 +2330,66 @@ fn global_search(cx: &mut Context) {
20912330 err
20922331 );
20932332 }
2094− WalkState::Continue
2333+ if stop {
2334+ WalkState::Quit
2335+ } else {
2336+ WalkState::Continue
2337+ }
20952338 })
20962339 });
2340+ });
2341+
2342+ cx.jobs.callback(async move {
2343+ let call = move |_: &mut Editor, compositor: &mut Compositor| {
2344+ let picker = Picker::with_stream(
2345+ picker,
2346+ injector,
2347+ move |cx, FileResult { path, line_num }, action| {
2348+ let doc = match cx.editor.open(path, action) {
2349+ Ok(id) => doc_mut!(cx.editor, &id),
2350+ Err(e) => {
2351+ cx.editor.set_error(format!(
2352+ "Failed to open file '{}': {}",
2353+ path.display(),
2354+ e
2355+ ));
2356+ return;
2357+ }
2358+ };
2359+
2360+ let line_num = *line_num;
2361+ let view = view_mut!(cx.editor);
2362+ let text = doc.text();
2363+ if line_num >= text.len_lines() {
2364+ cx.editor.set_error(
2365+ "The line you jumped to does not exist anymore because the file has changed.",
2366+ );
2367+ return;
2368+ }
2369+ let start = text.line_to_char(line_num);
2370+ let end = text.line_to_char((line_num + 1).min(text.len_lines()));
2371+
2372+ doc.set_selection(view.id, Selection::single(start, end));
2373+ if action.align_view(view, doc.id()) {
2374+ align_view(doc, view, Align::Center);
2375+ }
2376+ },
2377+ )
2378+ .with_preview(
2379+ |_editor, FileResult { path, line_num }| {
2380+ Some((path.clone().into(), Some((*line_num, *line_num))))
2381+ },
2382+ );
2383+ compositor.push(Box::new(overlaid(picker)))
2384+ };
2385+ Ok(Callback::EditorCompositor(Box::new(call)))
2386+ })
20972387 } else {
20982388 // Otherwise do nothing
20992389 // log::warn!("Global Search Invalid Pattern")
21002390 }
21012391 },
21022392 );
2103−
2104− let current_path = doc_mut!(cx.editor).path().cloned();
2105−
2106− let show_picker = async move {
2107− let all_matches: Vec<FileResult> =
2108− UnboundedReceiverStream::new(all_matches_rx).collect().await;
2109− let call: job::Callback = Callback::EditorCompositor(Box::new(
2110− move |editor: &mut Editor, compositor: &mut Compositor| {
2111− if all_matches.is_empty() {
2112− editor.set_status("No matches found");
2113− return;
2114− }
2115−
2116− let picker = FilePicker::new(
2117− all_matches,
2118− current_path,
2119− move |cx, FileResult { path, line_num }, action| {
2120− match cx.editor.open(path, action) {
2121− Ok(_) => {}
2122− Err(e) => {
2123− cx.editor.set_error(format!(
2124− "Failed to open file '{}': {}",
2125− path.display(),
2126− e
2127− ));
2128− return;
2129− }
2130− }
2131−
2132− let line_num = *line_num;
2133− let (view, doc) = current!(cx.editor);
2134− let text = doc.text();
2135− if line_num >= text.len_lines() {
2136− cx.editor.set_error("The line you jumped to does not exist anymore because the file has changed.");
2137− return;
2138− }
2139− let start = text.line_to_char(line_num);
2140− let end = text.line_to_char((line_num + 1).min(text.len_lines()));
2141−
2142− doc.set_selection(view.id, Selection::single(start, end));
2143− align_view(doc, view, Align::Center);
2144− },
2145− |_editor, FileResult { path, line_num }| {
2146− Some((path.clone().into(), Some((*line_num, *line_num))))
2147− },
2148− );
2149− compositor.push(Box::new(overlayed(picker)));
2150− },
2151− ));
2152− Ok(call)
2153− };
2154− cx.jobs.callback(show_picker);
21552393}
21562394
21572395enum Extend {
@@ -2274,23 +2512,40 @@ enum Operation {
22742512 Change,
22752513}
22762514
2515+fn selection_is_linewise(selection: &Selection, text: &Rope) -> bool {
2516+ selection.ranges().iter().all(|range| {
2517+ let text = text.slice(..);
2518+ if range.slice(text).len_lines() < 2 {
2519+ return false;
2520+ }
2521+ // If the start of the selection is at the start of a line and the end at the end of a line.
2522+ let (start_line, end_line) = range.line_range(text);
2523+ let start = text.line_to_char(start_line);
2524+ let end = text.line_to_char((end_line + 1).min(text.len_lines()));
2525+ start == range.from() && end == range.to()
2526+ })
2527+}
2528+
22772529fn delete_selection_impl(cx: &mut Context, op: Operation) {
22782530 let (view, doc) = current!(cx.editor);
22792531
22802532 let selection = doc.selection(view.id);
2533+ let only_whole_lines = selection_is_linewise(selection, doc.text());
22812534
22822535 if cx.register != Some('_') {
22832536 // first yank the selection
22842537 let text = doc.text().slice(..);
22852538 let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
22862539 let reg_name = cx.register.unwrap_or('"');
2287− cx.editor.registers.write(reg_name, values);
2540+ if let Err(err) = cx.editor.registers.write(reg_name, values) {
2541+ cx.editor.set_error(err.to_string());
2542+ return;
2543+ }
22882544 };
22892545
22902546 // then delete
2291− let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
2292− (range.from(), range.to(), None)
2293− });
2547+ let transaction =
2548+ Transaction::delete_by_selection(doc.text(), selection, |range| (range.from(), range.to()));
22942549 doc.apply(&transaction, view.id);
22952550
22962551 match op {
@@ -2299,17 +2554,59 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) {
22992554 exit_select_mode(cx);
23002555 }
23012556 Operation::Change => {
2302− enter_insert_mode(cx);
2557+ if only_whole_lines {
2558+ open_above(cx);
2559+ } else {
2560+ enter_insert_mode(cx);
2561+ }
23032562 }
23042563 }
23052564}
23062565
23072566#[inline]
2308−fn delete_selection_insert_mode(doc: &mut Document, view: &mut View, selection: &Selection) {
2309− let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
2310− (range.from(), range.to(), None)
2311− });
2567+fn delete_by_selection_insert_mode(
2568+ cx: &mut Context,
2569+ mut f: impl FnMut(RopeSlice, &Range) -> Deletion,
2570+ direction: Direction,
2571+) {
2572+ let (view, doc) = current!(cx.editor);
2573+ let text = doc.text().slice(..);
2574+ let mut selection = SmallVec::new();
2575+ let mut insert_newline = false;
2576+ let text_len = text.len_chars();
2577+ let mut transaction =
2578+ Transaction::delete_by_selection(doc.text(), doc.selection(view.id), |range| {
2579+ let (start, end) = f(text, range);
2580+ if direction == Direction::Forward {
2581+ let mut range = *range;
2582+ if range.head > range.anchor {
2583+ insert_newline |= end == text_len;
2584+ // move the cursor to the right so that the selection
2585+ // doesn't shrink when deleting forward (so the text appears to
2586+ // move to left)
2587+ // += 1 is enough here as the range is normalized to grapheme boundaries
2588+ // later anyway
2589+ range.head += 1;
2590+ }
2591+ selection.push(range);
2592+ }
2593+ (start, end)
2594+ });
2595+
2596+ // in case we delete the last character and the cursor would be moved to the EOF char
2597+ // insert a newline, just like when entering append mode
2598+ if insert_newline {
2599+ transaction = transaction.insert_at_eof(doc.line_ending.as_str().into());
2600+ }
2601+
2602+ if direction == Direction::Forward {
2603+ doc.set_selection(
2604+ view.id,
2605+ Selection::new(selection, doc.selection(view.id).primary_index()),
2606+ );
2607+ }
23122608 doc.apply(&transaction, view.id);
2609+ lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
23132610}
23142611
23152612fn delete_selection(cx: &mut Context) {
@@ -2383,6 +2680,10 @@ fn insert_mode(cx: &mut Context) {
23832680 .transform(|range| Range::new(range.to(), range.from()));
23842681
23852682 doc.set_selection(view.id, selection);
2683+
2684+ // [TODO] temporary workaround until we're not using the idle timer to
2685+ // trigger auto completions any more
2686+ cx.editor.clear_idle_timer();
23862687}
23872688
23882689// inserts at the end of each selection
@@ -2418,11 +2719,13 @@ fn append_mode(cx: &mut Context) {
24182719}
24192720
24202721fn file_picker(cx: &mut Context) {
2421− // We don't specify language markers, root will be the root of the current
2422− // git repo or the current dir if we're not in a repo
2423− let root = find_root(None, &[]);
2722+ let root = find_workspace().0;
2723+ if !root.exists() {
2724+ cx.editor.set_error("Workspace directory does not exist");
2725+ return;
2726+ }
24242727 let picker = ui::file_picker(root, &cx.editor.config());
2425− cx.push_layer(Box::new(overlayed(picker)));
2728+ cx.push_layer(Box::new(overlaid(picker)));
24262729}
24272730
24282731fn file_picker_in_current_buffer_directory(cx: &mut Context) {
@@ -2439,12 +2742,18 @@ fn file_picker_in_current_buffer_directory(cx: &mut Context) {
24392742 };
24402743
24412744 let picker = ui::file_picker(path, &cx.editor.config());
2442− cx.push_layer(Box::new(overlayed(picker)));
2745+ cx.push_layer(Box::new(overlaid(picker)));
24432746}
2747+
24442748fn file_picker_in_current_directory(cx: &mut Context) {
2445− let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("./"));
2749+ let cwd = helix_loader::current_working_dir();
2750+ if !cwd.exists() {
2751+ cx.editor
2752+ .set_error("Current working directory does not exist");
2753+ return;
2754+ }
24462755 let picker = ui::file_picker(cwd, &cx.editor.config());
2447− cx.push_layer(Box::new(overlayed(picker)));
2756+ cx.push_layer(Box::new(overlaid(picker)));
24482757}
24492758
24502759fn buffer_picker(cx: &mut Context) {
@@ -2455,6 +2764,7 @@ fn buffer_picker(cx: &mut Context) {
24552764 path: Option<PathBuf>,
24562765 is_modified: bool,
24572766 is_current: bool,
2767+ focused_at: std::time::Instant,
24582768 }
24592769
24602770 impl ui::menu::Item for BufferMeta {
@@ -2487,29 +2797,32 @@ fn buffer_picker(cx: &mut Context) {
24872797 path: doc.path().cloned(),
24882798 is_modified: doc.is_modified(),
24892799 is_current: doc.id() == current,
2800+ focused_at: doc.focused_at,
24902801 };
24912802
2492− let picker = FilePicker::new(
2493− cx.editor
2494− .documents
2495− .values()
2496− .map(|doc| new_meta(doc))
2497− .collect(),
2498− (),
2499− |cx, meta, action| {
2500− cx.editor.switch(meta.id, action);
2501− },
2502− |editor, meta| {
2503− let doc = &editor.documents.get(&meta.id)?;
2504− let &view_id = doc.selections().keys().next()?;
2505− let line = doc
2506− .selection(view_id)
2507− .primary()
2508− .cursor_line(doc.text().slice(..));
2509− Some((meta.id.into(), Some((line, line))))
2510− },
2511− );
2512− cx.push_layer(Box::new(overlayed(picker)));
2803+ let mut items = cx
2804+ .editor
2805+ .documents
2806+ .values()
2807+ .map(|doc| new_meta(doc))
2808+ .collect::<Vec<BufferMeta>>();
2809+
2810+ // mru
2811+ items.sort_unstable_by_key(|item| std::cmp::Reverse(item.focused_at));
2812+
2813+ let picker = Picker::new(items, (), |cx, meta, action| {
2814+ cx.editor.switch(meta.id, action);
2815+ })
2816+ .with_preview(|editor, meta| {
2817+ let doc = &editor.documents.get(&meta.id)?;
2818+ let &view_id = doc.selections().keys().next()?;
2819+ let line = doc
2820+ .selection(view_id)
2821+ .primary()
2822+ .cursor_line(doc.text().slice(..));
2823+ Some((meta.id.into(), Some((line, line))))
2824+ });
2825+ cx.push_layer(Box::new(overlaid(picker)));
25132826}
25142827
25152828fn jumplist_picker(cx: &mut Context) {
@@ -2548,6 +2861,13 @@ fn jumplist_picker(cx: &mut Context) {
25482861 }
25492862 }
25502863
2864+ for (view, _) in cx.editor.tree.views_mut() {
2865+ for doc_id in view.jumps.iter().map(|e| e.0).collect::<Vec<_>>().iter() {
2866+ let doc = doc_mut!(cx.editor, doc_id);
2867+ view.sync_changes(doc);
2868+ }
2869+ }
2870+
25512871 let new_meta = |view: &View, doc_id: DocumentId, selection: Selection| {
25522872 let doc = &cx.editor.documents.get(&doc_id);
25532873 let text = doc.map_or("".into(), |d| {
@@ -2567,7 +2887,7 @@ fn jumplist_picker(cx: &mut Context) {
25672887 }
25682888 };
25692889
2570− let picker = FilePicker::new(
2890+ let picker = Picker::new(
25712891 cx.editor
25722892 .tree
25732893 .views()
@@ -2581,17 +2901,19 @@ fn jumplist_picker(cx: &mut Context) {
25812901 |cx, meta, action| {
25822902 cx.editor.switch(meta.id, action);
25832903 let config = cx.editor.config();
2584− let (view, doc) = current!(cx.editor);
2904+ let (view, doc) = (view_mut!(cx.editor), doc_mut!(cx.editor, &meta.id));
25852905 doc.set_selection(view.id, meta.selection.clone());
2586− view.ensure_cursor_in_view_center(doc, config.scrolloff);
2587− },
2588− |editor, meta| {
2589− let doc = &editor.documents.get(&meta.id)?;
2590− let line = meta.selection.primary().cursor_line(doc.text().slice(..));
2591− Some((meta.path.clone()?.into(), Some((line, line))))
2906+ if action.align_view(view, doc.id()) {
2907+ view.ensure_cursor_in_view_center(doc, config.scrolloff);
2908+ }
25922909 },
2593− );
2594− cx.push_layer(Box::new(overlayed(picker)));
2910+ )
2911+ .with_preview(|editor, meta| {
2912+ let doc = &editor.documents.get(&meta.id)?;
2913+ let line = meta.selection.primary().cursor_line(doc.text().slice(..));
2914+ Some((meta.id.into(), Some((line, line))))
2915+ });
2916+ cx.push_layer(Box::new(overlaid(picker)));
25952917}
25962918
25972919impl ui::menu::Item for MappableCommand {
@@ -2624,6 +2946,9 @@ impl ui::menu::Item for MappableCommand {
26242946}
26252947
26262948pub fn command_palette(cx: &mut Context) {
2949+ let register = cx.register;
2950+ let count = cx.count;
2951+
26272952 cx.callback = Some(Box::new(
26282953 move |compositor: &mut Compositor, cx: &mut compositor::Context| {
26292954 let keymap = compositor.find::<ui::EditorView>().unwrap().keymaps.map()
@@ -2641,8 +2966,8 @@ pub fn command_palette(cx: &mut Context) {
26412966
26422967 let picker = Picker::new(commands, keymap, move |cx, command, _action| {
26432968 let mut ctx = Context {
2644− register: None,
2645− count: std::num::NonZeroUsize::new(1),
2969+ register,
2970+ count,
26462971 editor: cx.editor,
26472972 callback: None,
26482973 on_next_key_callback: None,
@@ -2665,7 +2990,7 @@ pub fn command_palette(cx: &mut Context) {
26652990 }
26662991 }
26672992 });
2668− compositor.push(Box::new(overlayed(picker)));
2993+ compositor.push(Box::new(overlaid(picker)));
26692994 },
26702995 ));
26712996}
@@ -2681,34 +3006,98 @@ fn last_picker(cx: &mut Context) {
26813006 }));
26823007}
26833008
2684−// I inserts at the first nonwhitespace character of each line with a selection
3009+/// Fallback position to use for [`insert_with_indent`].
3010+enum IndentFallbackPos {
3011+ LineStart,
3012+ LineEnd,
3013+}
3014+
3015+// `I` inserts at the first nonwhitespace character of each line with a selection.
3016+// If the line is empty, automatically indent.
26853017fn insert_at_line_start(cx: &mut Context) {
2686− goto_first_nonwhitespace(cx);
2687− enter_insert_mode(cx);
3018+ insert_with_indent(cx, IndentFallbackPos::LineStart);
26883019}
26893020
2690−// A inserts at the end of each line with a selection
3021+// `A` inserts at the end of each line with a selection.
3022+// If the line is empty, automatically indent.
26913023fn insert_at_line_end(cx: &mut Context) {
3024+ insert_with_indent(cx, IndentFallbackPos::LineEnd);
3025+}
3026+
3027+// Enter insert mode and auto-indent the current line if it is empty.
3028+// If the line is not empty, move the cursor to the specified fallback position.
3029+fn insert_with_indent(cx: &mut Context, cursor_fallback: IndentFallbackPos) {
26923030 enter_insert_mode(cx);
3031+
26933032 let (view, doc) = current!(cx.editor);
26943033
2695− let selection = doc.selection(view.id).clone().transform(|range| {
2696− let text = doc.text().slice(..);
2697− let line = range.cursor_line(text);
2698− let pos = line_end_char_index(&text, line);
2699− Range::new(pos, pos)
2700− });
2701− doc.set_selection(view.id, selection);
2702−}
3034+ let text = doc.text().slice(..);
3035+ let contents = doc.text();
3036+ let selection = doc.selection(view.id);
27033037
2704−// Creates an LspCallback that waits for formatting changes to be computed. When they're done,
2705−// it applies them, but only if the doc hasn't changed.
2706−//
2707−// TODO: provide some way to cancel this, probably as part of a more general job cancellation
2708−// scheme
2709−async fn make_format_callback(
2710− doc_id: DocumentId,
2711− doc_version: i32,
3038+ let language_config = doc.language_config();
3039+ let syntax = doc.syntax();
3040+ let tab_width = doc.tab_width();
3041+
3042+ let mut ranges = SmallVec::with_capacity(selection.len());
3043+ let mut offs = 0;
3044+
3045+ let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
3046+ let cursor_line = range.cursor_line(text);
3047+ let cursor_line_start = text.line_to_char(cursor_line);
3048+
3049+ if line_end_char_index(&text, cursor_line) == cursor_line_start {
3050+ // line is empty => auto indent
3051+ let line_end_index = cursor_line_start;
3052+
3053+ let indent = indent::indent_for_newline(
3054+ language_config,
3055+ syntax,
3056+ &doc.config.load().indent_heuristic,
3057+ &doc.indent_style,
3058+ tab_width,
3059+ text,
3060+ cursor_line,
3061+ line_end_index,
3062+ cursor_line,
3063+ );
3064+
3065+ // calculate new selection ranges
3066+ let pos = offs + cursor_line_start;
3067+ let indent_width = indent.chars().count();
3068+ ranges.push(Range::point(pos + indent_width));
3069+ offs += indent_width;
3070+
3071+ (line_end_index, line_end_index, Some(indent.into()))
3072+ } else {
3073+ // move cursor to the fallback position
3074+ let pos = match cursor_fallback {
3075+ IndentFallbackPos::LineStart => {
3076+ find_first_non_whitespace_char(text.line(cursor_line))
3077+ .map(|ws_offset| ws_offset + cursor_line_start)
3078+ .unwrap_or(cursor_line_start)
3079+ }
3080+ IndentFallbackPos::LineEnd => line_end_char_index(&text, cursor_line),
3081+ };
3082+
3083+ ranges.push(range.put_cursor(text, pos + offs, cx.editor.mode == Mode::Select));
3084+
3085+ (cursor_line_start, cursor_line_start, None)
3086+ }
3087+ });
3088+
3089+ transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
3090+ doc.apply(&transaction, view.id);
3091+}
3092+
3093+// Creates an LspCallback that waits for formatting changes to be computed. When they're done,
3094+// it applies them, but only if the doc hasn't changed.
3095+//
3096+// TODO: provide some way to cancel this, probably as part of a more general job cancellation
3097+// scheme
3098+async fn make_format_callback(
3099+ doc_id: DocumentId,
3100+ doc_version: i32,
27123101 view_id: ViewId,
27133102 format: impl Future<Output = Result<Transaction, FormatterError>> + Send + 'static,
27143103 write: Option<(Option<PathBuf>, bool)>,
@@ -2769,6 +3158,7 @@ fn open(cx: &mut Context, open: Open) {
27693158 Open::Below => graphemes::prev_grapheme_boundary(text, range.to()),
27703159 Open::Above => range.from(),
27713160 });
3161+
27723162 let new_line = match open {
27733163 // adjust position to the end of the line (next line - 1)
27743164 Open::Below => cursor_line + 1,
@@ -2776,13 +3166,15 @@ fn open(cx: &mut Context, open: Open) {
27763166 Open::Above => cursor_line,
27773167 };
27783168
3169+ let line_num = new_line.saturating_sub(1);
3170+
27793171 // Index to insert newlines after, as well as the char width
27803172 // to use to compensate for those inserted newlines.
27813173 let (line_end_index, line_end_offset_width) = if new_line == 0 {
27823174 (0, 0)
27833175 } else {
27843176 (
2785− line_end_char_index(&doc.text().slice(..), new_line.saturating_sub(1)),
3177+ line_end_char_index(&text, line_num),
27863178 doc.line_ending.len_chars(),
27873179 )
27883180 };
@@ -2790,13 +3182,15 @@ fn open(cx: &mut Context, open: Open) {
27903182 let indent = indent::indent_for_newline(
27913183 doc.language_config(),
27923184 doc.syntax(),
3185+ &doc.config.load().indent_heuristic,
27933186 &doc.indent_style,
27943187 doc.tab_width(),
27953188 text,
2796− new_line.saturating_sub(1),
3189+ line_num,
27973190 line_end_index,
27983191 cursor_line,
27993192 );
3193+
28003194 let indent_len = indent.len();
28013195 let mut text = String::with_capacity(1 + indent_len);
28023196 text.push_str(doc.line_ending.as_str());
@@ -2956,7 +3350,7 @@ fn exit_select_mode(cx: &mut Context) {
29563350
29573351fn goto_first_diag(cx: &mut Context) {
29583352 let (view, doc) = current!(cx.editor);
2959− let selection = match doc.diagnostics().first() {
3353+ let selection = match doc.shown_diagnostics().next() {
29603354 Some(diag) => Selection::single(diag.range.start, diag.range.end),
29613355 None => return,
29623356 };
@@ -2965,7 +3359,7 @@ fn goto_first_diag(cx: &mut Context) {
29653359
29663360fn goto_last_diag(cx: &mut Context) {
29673361 let (view, doc) = current!(cx.editor);
2968− let selection = match doc.diagnostics().last() {
3362+ let selection = match doc.shown_diagnostics().last() {
29693363 Some(diag) => Selection::single(diag.range.start, diag.range.end),
29703364 None => return,
29713365 };
@@ -2981,10 +3375,9 @@ fn goto_next_diag(cx: &mut Context) {
29813375 .cursor(doc.text().slice(..));
29823376
29833377 let diag = doc
2984− .diagnostics()
2985− .iter()
3378+ .shown_diagnostics()
29863379 .find(|diag| diag.range.start > cursor_pos)
2987− .or_else(|| doc.diagnostics().first());
3380+ .or_else(|| doc.shown_diagnostics().next());
29883381
29893382 let selection = match diag {
29903383 Some(diag) => Selection::single(diag.range.start, diag.range.end),
@@ -3002,11 +3395,10 @@ fn goto_prev_diag(cx: &mut Context) {
30023395 .cursor(doc.text().slice(..));
30033396
30043397 let diag = doc
3005− .diagnostics()
3006− .iter()
3398+ .shown_diagnostics()
30073399 .rev()
30083400 .find(|diag| diag.range.start < cursor_pos)
3009− .or_else(|| doc.diagnostics().last());
3401+ .or_else(|| doc.shown_diagnostics().last());
30103402
30113403 let selection = match diag {
30123404 // NOTE: the selection is reversed because we're jumping to the
@@ -3097,8 +3489,7 @@ fn goto_next_change_impl(cx: &mut Context, direction: Direction) {
30973489
30983490 doc.set_selection(view.id, selection)
30993491 };
3100− motion(cx.editor);
3101− cx.editor.last_motion = Some(Motion(Box::new(motion)));
3492+ cx.editor.apply_motion(motion);
31023493}
31033494
31043495/// Returns the [Range] for a [Hunk] in the given text.
@@ -3161,23 +3552,19 @@ pub mod insert {
31613552 use helix_lsp::lsp;
31623553 // if ch matches completion char, trigger completion
31633554 let doc = doc_mut!(cx.editor);
3164− let language_server = match doc.language_server() {
3165− Some(language_server) => language_server,
3166− None => return,
3167− };
3168−
3169− let capabilities = language_server.capabilities();
3555+ let trigger_completion = doc
3556+ .language_servers_with_feature(LanguageServerFeature::Completion)
3557+ .any(|ls| {
3558+ // TODO: what if trigger is multiple chars long
3559+ matches!(&ls.capabilities().completion_provider, Some(lsp::CompletionOptions {
3560+ trigger_characters: Some(triggers),
3561+ ..
3562+ }) if triggers.iter().any(|trigger| trigger.contains(ch)))
3563+ });
31703564
3171− if let Some(lsp::CompletionOptions {
3172− trigger_characters: Some(triggers),
3173− ..
3174− }) = &capabilities.completion_provider
3175− {
3176− // TODO: what if trigger is multiple chars long
3177− if triggers.iter().any(|trigger| trigger.contains(ch)) {
3178− cx.editor.clear_idle_timer();
3179− super::completion(cx);
3180− }
3565+ if trigger_completion {
3566+ cx.editor.clear_idle_timer();
3567+ super::completion(cx);
31813568 }
31823569 }
31833570
@@ -3185,12 +3572,12 @@ pub mod insert {
31853572 use helix_lsp::lsp;
31863573 // if ch matches signature_help char, trigger
31873574 let doc = doc_mut!(cx.editor);
3188− // The language_server!() macro is not used here since it will
3189− // print an "LSP not active for current buffer" message on
3190− // every keypress.
3191− let language_server = match doc.language_server() {
3192− Some(language_server) => language_server,
3193− None => return,
3575+ // TODO support multiple language servers (not just the first that is found), likely by merging UI somehow
3576+ let Some(language_server) = doc
3577+ .language_servers_with_feature(LanguageServerFeature::SignatureHelp)
3578+ .next()
3579+ else {
3580+ return;
31943581 };
31953582
31963583 let capabilities = language_server.capabilities();
@@ -3229,6 +3616,7 @@ pub mod insert {
32293616 }
32303617
32313618 use helix_core::auto_pairs;
3619+ use helix_view::editor::SmartTabConfig;
32323620
32333621 pub fn insert_char(cx: &mut Context, c: char) {
32343622 let (view, doc) = current_ref!(cx.editor);
@@ -3254,6 +3642,31 @@ pub mod insert {
32543642 }
32553643 }
32563644
3645+ pub fn smart_tab(cx: &mut Context) {
3646+ let (view, doc) = current_ref!(cx.editor);
3647+ let view_id = view.id;
3648+
3649+ if matches!(
3650+ cx.editor.config().smart_tab,
3651+ Some(SmartTabConfig { enable: true, .. })
3652+ ) {
3653+ let cursors_after_whitespace = doc.selection(view_id).ranges().iter().all(|range| {
3654+ let cursor = range.cursor(doc.text().slice(..));
3655+ let current_line_num = doc.text().char_to_line(cursor);
3656+ let current_line_start = doc.text().line_to_char(current_line_num);
3657+ let left = doc.text().slice(current_line_start..cursor);
3658+ left.chars().all(|c| c.is_whitespace())
3659+ });
3660+
3661+ if !cursors_after_whitespace {
3662+ move_parent_node_end(cx);
3663+ return;
3664+ }
3665+ }
3666+
3667+ insert_tab(cx);
3668+ }
3669+
32573670 pub fn insert_tab(cx: &mut Context) {
32583671 let (view, doc) = current!(cx.editor);
32593672 // TODO: round out to nearest indentation level (for example a line with 3 spaces should
@@ -3309,6 +3722,7 @@ pub mod insert {
33093722 let indent = indent::indent_for_newline(
33103723 doc.language_config(),
33113724 doc.syntax(),
3725+ &doc.config.load().indent_heuristic,
33123726 &doc.indent_style,
33133727 doc.tab_width(),
33143728 text,
@@ -3382,10 +3796,10 @@ pub mod insert {
33823796 let auto_pairs = doc.auto_pairs(cx.editor);
33833797
33843798 let transaction =
3385− Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3799+ Transaction::delete_by_selection(doc.text(), doc.selection(view.id), |range| {
33863800 let pos = range.cursor(text);
33873801 if pos == 0 {
3388− return (pos, pos, None);
3802+ return (pos, pos);
33893803 }
33903804 let line_start_pos = text.line_to_char(range.cursor_line(text));
33913805 // consider to delete by indent level if all characters before `pos` are indent units.
@@ -3393,11 +3807,7 @@ pub mod insert {
33933807 if !fragment.is_empty() && fragment.chars().all(|ch| ch == ' ' || ch == '\t') {
33943808 if text.get_char(pos.saturating_sub(1)) == Some('\t') {
33953809 // fast path, delete one char
3396− (
3397− graphemes::nth_prev_grapheme_boundary(text, pos, 1),
3398− pos,
3399− None,
3400− )
3810+ (graphemes::nth_prev_grapheme_boundary(text, pos, 1), pos)
34013811 } else {
34023812 let width: usize = fragment
34033813 .chars()
@@ -3424,7 +3834,7 @@ pub mod insert {
34243834 _ => break,
34253835 }
34263836 }
3427− (start, pos, None) // delete!
3837+ (start, pos) // delete!
34283838 }
34293839 } else {
34303840 match (
@@ -3442,17 +3852,12 @@ pub mod insert {
34423852 (
34433853 graphemes::nth_prev_grapheme_boundary(text, pos, count),
34443854 graphemes::nth_next_grapheme_boundary(text, pos, count),
3445− None,
34463855 )
34473856 }
34483857 _ =>
34493858 // delete 1 char
34503859 {
3451− (
3452− graphemes::nth_prev_grapheme_boundary(text, pos, count),
3453− pos,
3454− None,
3455− )
3860+ (graphemes::nth_prev_grapheme_boundary(text, pos, count), pos)
34563861 }
34573862 }
34583863 }
@@ -3465,50 +3870,40 @@ pub mod insert {
34653870
34663871 pub fn delete_char_forward(cx: &mut Context) {
34673872 let count = cx.count();
3468− let (view, doc) = current!(cx.editor);
3469− let text = doc.text().slice(..);
3470− let transaction =
3471− Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3873+ delete_by_selection_insert_mode(
3874+ cx,
3875+ |text, range| {
34723876 let pos = range.cursor(text);
3473− (
3474− pos,
3475− graphemes::nth_next_grapheme_boundary(text, pos, count),
3476− None,
3477− )
3478− });
3479− doc.apply(&transaction, view.id);
3480−
3481− lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
3877+ (pos, graphemes::nth_next_grapheme_boundary(text, pos, count))
3878+ },
3879+ Direction::Forward,
3880+ )
34823881 }
34833882
34843883 pub fn delete_word_backward(cx: &mut Context) {
34853884 let count = cx.count();
3486− let (view, doc) = current!(cx.editor);
3487− let text = doc.text().slice(..);
3488−
3489− let selection = doc.selection(view.id).clone().transform(|range| {
3490− let anchor = movement::move_prev_word_start(text, range, count).from();
3491− let next = Range::new(anchor, range.cursor(text));
3492− exclude_cursor(text, next, range)
3493− });
3494− delete_selection_insert_mode(doc, view, &selection);
3495−
3496− lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
3885+ delete_by_selection_insert_mode(
3886+ cx,
3887+ |text, range| {
3888+ let anchor = movement::move_prev_word_start(text, *range, count).from();
3889+ let next = Range::new(anchor, range.cursor(text));
3890+ let range = exclude_cursor(text, next, *range);
3891+ (range.from(), range.to())
3892+ },
3893+ Direction::Backward,
3894+ );
34973895 }
34983896
34993897 pub fn delete_word_forward(cx: &mut Context) {
35003898 let count = cx.count();
3501− let (view, doc) = current!(cx.editor);
3502− let text = doc.text().slice(..);
3503−
3504− let selection = doc.selection(view.id).clone().transform(|range| {
3505− let head = movement::move_next_word_end(text, range, count).to();
3506− Range::new(range.cursor(text), head)
3507− });
3508−
3509− delete_selection_insert_mode(doc, view, &selection);
3510−
3511− lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
3899+ delete_by_selection_insert_mode(
3900+ cx,
3901+ |text, range| {
3902+ let head = movement::move_next_word_end(text, *range, count).to();
3903+ (range.cursor(text), head)
3904+ },
3905+ Direction::Forward,
3906+ );
35123907 }
35133908}
35143909
@@ -3568,34 +3963,21 @@ fn commit_undo_checkpoint(cx: &mut Context) {
35683963// Yank / Paste
35693964
35703965fn yank(cx: &mut Context) {
3571− let (view, doc) = current!(cx.editor);
3572− let text = doc.text().slice(..);
3573−
3574− let values: Vec<String> = doc
3575− .selection(view.id)
3576− .fragments(text)
3577− .map(Cow::into_owned)
3578− .collect();
3579−
3580− let msg = format!(
3581− "yanked {} selection(s) to register {}",
3582− values.len(),
3583− cx.register.unwrap_or('"')
3584− );
3966+ yank_impl(cx.editor, cx.register.unwrap_or('"'));
3967+ exit_select_mode(cx);
3968+}
35853969
3586− cx.editor
3587− .registers
3588− .write(cx.register.unwrap_or('"'), values);
3970+fn yank_to_clipboard(cx: &mut Context) {
3971+ yank_impl(cx.editor, '+');
3972+ exit_select_mode(cx);
3973+}
35893974
3590− cx.editor.set_status(msg);
3975+fn yank_to_primary_clipboard(cx: &mut Context) {
3976+ yank_impl(cx.editor, '*');
35913977 exit_select_mode(cx);
35923978}
35933979
3594−fn yank_joined_to_clipboard_impl(
3595− editor: &mut Editor,
3596− separator: &str,
3597− clipboard_type: ClipboardType,
3598−) -> anyhow::Result<()> {
3980+fn yank_impl(editor: &mut Editor, register: char) {
35993981 let (view, doc) = current!(editor);
36003982 let text = doc.text().slice(..);
36013983
@@ -3604,74 +3986,79 @@ fn yank_joined_to_clipboard_impl(
36043986 .fragments(text)
36053987 .map(Cow::into_owned)
36063988 .collect();
3989+ let selections = values.len();
36073990
3608− let clipboard_text = match clipboard_type {
3609− ClipboardType::Clipboard => "system clipboard",
3610− ClipboardType::Selection => "primary clipboard",
3611− };
3612−
3613− let msg = format!(
3614− "joined and yanked {} selection(s) to {}",
3615− values.len(),
3616− clipboard_text,
3617− );
3991+ match editor.registers.write(register, values) {
3992+ Ok(_) => editor.set_status(format!(
3993+ "yanked {selections} selection{} to register {register}",
3994+ if selections == 1 { "" } else { "s" }
3995+ )),
3996+ Err(err) => editor.set_error(err.to_string()),
3997+ }
3998+}
36183999
3619− let joined = values.join(separator);
4000+fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
4001+ let (view, doc) = current!(editor);
4002+ let text = doc.text().slice(..);
36204003
3621− editor
3622− .clipboard_provider
3623− .set_contents(joined, clipboard_type)
3624− .context("Couldn't set system clipboard content")?;
4004+ let selection = doc.selection(view.id);
4005+ let selections = selection.len();
4006+ let joined = selection
4007+ .fragments(text)
4008+ .fold(String::new(), |mut acc, fragment| {
4009+ if !acc.is_empty() {
4010+ acc.push_str(separator);
4011+ }
4012+ acc.push_str(&fragment);
4013+ acc
4014+ });
36254015
3626− editor.set_status(msg);
4016+ match editor.registers.write(register, vec![joined]) {
4017+ Ok(_) => editor.set_status(format!(
4018+ "joined and yanked {selections} selection{} to register {register}",
4019+ if selections == 1 { "" } else { "s" }
4020+ )),
4021+ Err(err) => editor.set_error(err.to_string()),
4022+ }
4023+}
36274024
3628− Ok(())
4025+fn yank_joined(cx: &mut Context) {
4026+ let separator = doc!(cx.editor).line_ending.as_str();
4027+ yank_joined_impl(cx.editor, separator, cx.register.unwrap_or('"'));
4028+ exit_select_mode(cx);
36294029}
36304030
36314031fn yank_joined_to_clipboard(cx: &mut Context) {
36324032 let line_ending = doc!(cx.editor).line_ending;
3633− let _ =
3634− yank_joined_to_clipboard_impl(cx.editor, line_ending.as_str(), ClipboardType::Clipboard);
4033+ yank_joined_impl(cx.editor, line_ending.as_str(), '+');
36354034 exit_select_mode(cx);
36364035}
36374036
3638−fn yank_main_selection_to_clipboard_impl(
3639− editor: &mut Editor,
3640− clipboard_type: ClipboardType,
3641−) -> anyhow::Result<()> {
4037+fn yank_joined_to_primary_clipboard(cx: &mut Context) {
4038+ let line_ending = doc!(cx.editor).line_ending;
4039+ yank_joined_impl(cx.editor, line_ending.as_str(), '*');
4040+ exit_select_mode(cx);
4041+}
4042+
4043+fn yank_primary_selection_impl(editor: &mut Editor, register: char) {
36424044 let (view, doc) = current!(editor);
36434045 let text = doc.text().slice(..);
36444046
3645− let message_text = match clipboard_type {
3646− ClipboardType::Clipboard => "yanked main selection to system clipboard",
3647− ClipboardType::Selection => "yanked main selection to primary clipboard",
3648− };
4047+ let selection = doc.selection(view.id).primary().fragment(text).to_string();
36494048
3650− let value = doc.selection(view.id).primary().fragment(text);
3651−
3652− if let Err(e) = editor
3653− .clipboard_provider
3654− .set_contents(value.into_owned(), clipboard_type)
3655− {
3656− bail!("Couldn't set system clipboard content: {}", e);
4049+ match editor.registers.write(register, vec![selection]) {
4050+ Ok(_) => editor.set_status(format!("yanked primary selection to register {register}",)),
4051+ Err(err) => editor.set_error(err.to_string()),
36574052 }
3658−
3659− editor.set_status(message_text);
3660− Ok(())
36614053}
36624054
36634055fn yank_main_selection_to_clipboard(cx: &mut Context) {
3664− let _ = yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Clipboard);
3665−}
3666−
3667−fn yank_joined_to_primary_clipboard(cx: &mut Context) {
3668− let line_ending = doc!(cx.editor).line_ending;
3669− let _ =
3670− yank_joined_to_clipboard_impl(cx.editor, line_ending.as_str(), ClipboardType::Selection);
4056+ yank_primary_selection_impl(cx.editor, '+');
4057+ exit_select_mode(cx);
36714058}
36724059
36734060fn yank_main_selection_to_primary_clipboard(cx: &mut Context) {
3674− let _ = yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection);
4061+ yank_primary_selection_impl(cx.editor, '*');
36754062 exit_select_mode(cx);
36764063}
36774064
@@ -3769,146 +4156,99 @@ pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) {
37694156 };
37704157 let (view, doc) = current!(cx.editor);
37714158 paste_impl(&[contents], doc, view, paste, count, cx.editor.mode);
3772−}
3773−
3774−fn paste_clipboard_impl(
3775− editor: &mut Editor,
3776− action: Paste,
3777− clipboard_type: ClipboardType,
3778− count: usize,
3779−) -> anyhow::Result<()> {
3780− let (view, doc) = current!(editor);
3781− match editor.clipboard_provider.get_contents(clipboard_type) {
3782− Ok(contents) => {
3783− paste_impl(&[contents], doc, view, action, count, editor.mode);
3784− Ok(())
3785− }
3786− Err(e) => Err(e.context("Couldn't get system clipboard contents")),
3787− }
4159+ exit_select_mode(cx);
37884160}
37894161
37904162fn paste_clipboard_after(cx: &mut Context) {
3791− let _ = paste_clipboard_impl(
3792− cx.editor,
3793− Paste::After,
3794− ClipboardType::Clipboard,
3795− cx.count(),
3796− );
4163+ paste(cx.editor, '+', Paste::After, cx.count());
4164+ exit_select_mode(cx);
37974165}
37984166
37994167fn paste_clipboard_before(cx: &mut Context) {
3800− let _ = paste_clipboard_impl(
3801− cx.editor,
3802− Paste::Before,
3803− ClipboardType::Clipboard,
3804− cx.count(),
3805− );
4168+ paste(cx.editor, '+', Paste::Before, cx.count());
4169+ exit_select_mode(cx);
38064170}
38074171
38084172fn paste_primary_clipboard_after(cx: &mut Context) {
3809− let _ = paste_clipboard_impl(
3810− cx.editor,
3811− Paste::After,
3812− ClipboardType::Selection,
3813− cx.count(),
3814− );
4173+ paste(cx.editor, '*', Paste::After, cx.count());
4174+ exit_select_mode(cx);
38154175}
38164176
38174177fn paste_primary_clipboard_before(cx: &mut Context) {
3818− let _ = paste_clipboard_impl(
3819− cx.editor,
3820− Paste::Before,
3821− ClipboardType::Selection,
3822− cx.count(),
3823− );
4178+ paste(cx.editor, '*', Paste::Before, cx.count());
4179+ exit_select_mode(cx);
38244180}
38254181
38264182fn replace_with_yanked(cx: &mut Context) {
3827− let count = cx.count();
3828− let reg_name = cx.register.unwrap_or('"');
3829− let (view, doc) = current!(cx.editor);
3830− let registers = &mut cx.editor.registers;
3831−
3832− if let Some(values) = registers.read(reg_name) {
3833− if !values.is_empty() {
3834− let repeat = std::iter::repeat(
3835− values
3836− .last()
3837− .map(|value| Tendril::from(&value.repeat(count)))
3838− .unwrap(),
3839− );
3840− let mut values = values
3841− .iter()
3842− .map(|value| Tendril::from(&value.repeat(count)))
3843− .chain(repeat);
3844− let selection = doc.selection(view.id);
3845− let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
3846− if !range.is_empty() {
3847− (range.from(), range.to(), Some(values.next().unwrap()))
3848− } else {
3849− (range.from(), range.to(), None)
3850− }
3851− });
3852−
3853− doc.apply(&transaction, view.id);
3854− exit_select_mode(cx);
3855− }
3856− }
4183+ replace_with_yanked_impl(cx.editor, cx.register.unwrap_or('"'), cx.count());
4184+ exit_select_mode(cx);
38574185}
38584186
3859−fn replace_selections_with_clipboard_impl(
3860− cx: &mut Context,
3861− clipboard_type: ClipboardType,
3862−) -> anyhow::Result<()> {
3863− let count = cx.count();
3864− let (view, doc) = current!(cx.editor);
4187+fn replace_with_yanked_impl(editor: &mut Editor, register: char, count: usize) {
4188+ let Some(values) = editor.registers
4189+ .read(register, editor)
4190+ .filter(|values| values.len() > 0) else { return };
4191+ let values: Vec<_> = values.map(|value| value.to_string()).collect();
38654192
3866− match cx.editor.clipboard_provider.get_contents(clipboard_type) {
3867− Ok(contents) => {
3868− let selection = doc.selection(view.id);
3869− let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
3870− (
3871− range.from(),
3872− range.to(),
3873− Some(contents.repeat(count).as_str().into()),
3874− )
3875− });
3876−
3877− doc.apply(&transaction, view.id);
3878− doc.append_changes_to_history(view);
4193+ let (view, doc) = current!(editor);
4194+ let repeat = std::iter::repeat(
4195+ values
4196+ .last()
4197+ .map(|value| Tendril::from(&value.repeat(count)))
4198+ .unwrap(),
4199+ );
4200+ let mut values = values
4201+ .iter()
4202+ .map(|value| Tendril::from(&value.repeat(count)))
4203+ .chain(repeat);
4204+ let selection = doc.selection(view.id);
4205+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
4206+ if !range.is_empty() {
4207+ (range.from(), range.to(), Some(values.next().unwrap()))
4208+ } else {
4209+ (range.from(), range.to(), None)
38794210 }
3880− Err(e) => return Err(e.context("Couldn't get system clipboard contents")),
3881− }
4211+ });
38824212
3883− exit_select_mode(cx);
3884− Ok(())
4213+ doc.apply(&transaction, view.id);
38854214}
38864215
38874216fn replace_selections_with_clipboard(cx: &mut Context) {
3888− let _ = replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard);
4217+ replace_with_yanked_impl(cx.editor, '+', cx.count());
4218+ exit_select_mode(cx);
38894219}
38904220
38914221fn replace_selections_with_primary_clipboard(cx: &mut Context) {
3892− let _ = replace_selections_with_clipboard_impl(cx, ClipboardType::Selection);
4222+ replace_with_yanked_impl(cx.editor, '*', cx.count());
4223+ exit_select_mode(cx);
38934224}
38944225
3895−fn paste(cx: &mut Context, pos: Paste) {
3896− let count = cx.count();
3897− let reg_name = cx.register.unwrap_or('"');
3898− let (view, doc) = current!(cx.editor);
3899− let registers = &mut cx.editor.registers;
4226+fn paste(editor: &mut Editor, register: char, pos: Paste, count: usize) {
4227+ let Some(values) = editor.registers.read(register, editor) else { return };
4228+ let values: Vec<_> = values.map(|value| value.to_string()).collect();
39004229
3901− if let Some(values) = registers.read(reg_name) {
3902− paste_impl(values, doc, view, pos, count, cx.editor.mode);
3903− }
4230+ let (view, doc) = current!(editor);
4231+ paste_impl(&values, doc, view, pos, count, editor.mode);
39044232}
39054233
39064234fn paste_after(cx: &mut Context) {
3907− paste(cx, Paste::After)
4235+ paste(
4236+ cx.editor,
4237+ cx.register.unwrap_or('"'),
4238+ Paste::After,
4239+ cx.count(),
4240+ );
4241+ exit_select_mode(cx);
39084242}
39094243
39104244fn paste_before(cx: &mut Context) {
3911− paste(cx, Paste::Before)
4245+ paste(
4246+ cx.editor,
4247+ cx.register.unwrap_or('"'),
4248+ Paste::Before,
4249+ cx.count(),
4250+ );
4251+ exit_select_mode(cx);
39124252}
39134253
39144254fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
@@ -3947,6 +4287,7 @@ fn indent(cx: &mut Context) {
39474287 }),
39484288 );
39494289 doc.apply(&transaction, view.id);
4290+ exit_select_mode(cx);
39504291}
39514292
39524293fn unindent(cx: &mut Context) {
@@ -3986,71 +4327,76 @@ fn unindent(cx: &mut Context) {
39864327 let transaction = Transaction::change(doc.text(), changes.into_iter());
39874328
39884329 doc.apply(&transaction, view.id);
4330+ exit_select_mode(cx);
39894331}
39904332
39914333fn format_selections(cx: &mut Context) {
39924334 use helix_lsp::{lsp, util::range_to_lsp_range};
39934335
39944336 let (view, doc) = current!(cx.editor);
4337+ let view_id = view.id;
39954338
39964339 // via lsp if available
39974340 // TODO: else via tree-sitter indentation calculations
39984341
3999− let language_server = match doc.language_server() {
4000− Some(language_server) => language_server,
4001− None => return,
4342+ if doc.selection(view_id).len() != 1 {
4343+ cx.editor
4344+ .set_error("format_selections only supports a single selection for now");
4345+ return;
4346+ }
4347+
4348+ // TODO extra LanguageServerFeature::FormatSelections?
4349+ // maybe such that LanguageServerFeature::Format contains it as well
4350+ let Some(language_server) = doc
4351+ .language_servers_with_feature(LanguageServerFeature::Format)
4352+ .find(|ls| {
4353+ matches!(
4354+ ls.capabilities().document_range_formatting_provider,
4355+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
4356+ )
4357+ })
4358+ else {
4359+ cx.editor
4360+ .set_error("No configured language server supports range formatting");
4361+ return;
40024362 };
40034363
4364+ let offset_encoding = language_server.offset_encoding();
40044365 let ranges: Vec<lsp::Range> = doc
4005− .selection(view.id)
4366+ .selection(view_id)
40064367 .iter()
4007− .map(|range| range_to_lsp_range(doc.text(), *range, language_server.offset_encoding()))
4368+ .map(|range| range_to_lsp_range(doc.text(), *range, offset_encoding))
40084369 .collect();
40094370
4010− if ranges.len() != 1 {
4011− cx.editor
4012− .set_error("format_selections only supports a single selection for now");
4013− return;
4014− }
4015−
40164371 // TODO: handle fails
40174372 // TODO: concurrent map over all ranges
40184373
40194374 let range = ranges[0];
40204375
4021− let request = match language_server.text_document_range_formatting(
4022− doc.identifier(),
4023− range,
4024− lsp::FormattingOptions::default(),
4025− None,
4026− ) {
4027− Some(future) => future,
4028− None => {
4029− cx.editor
4030− .set_error("Language server does not support range formatting");
4031− return;
4032− }
4033− };
4376+ let future = language_server
4377+ .text_document_range_formatting(
4378+ doc.identifier(),
4379+ range,
4380+ lsp::FormattingOptions::default(),
4381+ None,
4382+ )
4383+ .unwrap();
40344384
4035− let edits = tokio::task::block_in_place(|| helix_lsp::block_on(request)).unwrap_or_default();
4385+ let edits = tokio::task::block_in_place(|| helix_lsp::block_on(future)).unwrap_or_default();
40364386
4037− let transaction = helix_lsp::util::generate_transaction_from_edits(
4038− doc.text(),
4039− edits,
4040− language_server.offset_encoding(),
4041− );
4387+ let transaction =
4388+ helix_lsp::util::generate_transaction_from_edits(doc.text(), edits, offset_encoding);
40424389
4043− doc.apply(&transaction, view.id);
4390+ doc.apply(&transaction, view_id);
40444391}
40454392
40464393fn join_selections_impl(cx: &mut Context, select_space: bool) {
40474394 use movement::skip_while;
40484395 let (view, doc) = current!(cx.editor);
40494396 let text = doc.text();
4050− let slice = doc.text().slice(..);
4397+ let slice = text.slice(..);
40514398
40524399 let mut changes = Vec::new();
4053− let fragment = Tendril::from(" ");
40544400
40554401 for selection in doc.selection(view.id) {
40564402 let (start, mut end) = selection.line_range(slice);
@@ -4066,9 +4412,13 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
40664412 let mut end = text.line_to_char(line + 1);
40674413 end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
40684414
4069− // need to skip from start, not end
4070− let change = (start, end, Some(fragment.clone()));
4071− changes.push(change);
4415+ let separator = if end == line_end_char_index(&slice, line + 1) {
4416+ // the joining line contains only space-characters => don't include a whitespace when joining
4417+ None
4418+ } else {
4419+ Some(Tendril::from(" "))
4420+ };
4421+ changes.push((start, end, separator));
40724422 }
40734423 }
40744424
@@ -4080,9 +4430,6 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
40804430 changes.sort_unstable_by_key(|(from, _to, _text)| *from);
40814431 changes.dedup();
40824432
4083− // TODO: joining multiple empty lines should be replaced by a single space.
4084− // need to merge change ranges that touch
4085−
40864433 // select inserted spaces
40874434 let transaction = if select_space {
40884435 let ranges: SmallVec<_> = changes
@@ -4094,9 +4441,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
40944441 })
40954442 .collect();
40964443 let selection = Selection::new(ranges, 0);
4097− Transaction::change(doc.text(), changes.into_iter()).with_selection(selection)
4444+ Transaction::change(text, changes.into_iter()).with_selection(selection)
40984445 } else {
4099− Transaction::change(doc.text(), changes.into_iter())
4446+ Transaction::change(text, changes.into_iter())
41004447 };
41014448
41024449 doc.apply(&transaction, view.id);
@@ -4110,8 +4457,8 @@ fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) {
41104457 if remove { "remove:" } else { "keep:" }.into(),
41114458 Some(reg),
41124459 ui::completers::none,
4113− move |editor, regex, event| {
4114− let (view, doc) = current!(editor);
4460+ move |cx, regex, event| {
4461+ let (view, doc) = current!(cx.editor);
41154462 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
41164463 return;
41174464 }
@@ -4170,35 +4517,75 @@ pub fn completion(cx: &mut Context) {
41704517
41714518 let (view, doc) = current!(cx.editor);
41724519
4173− let language_server = match doc.language_server() {
4174− Some(language_server) => language_server,
4175− None => return,
4520+ let savepoint = if let Some(CompleteAction::Selected { savepoint }) = &cx.editor.last_completion
4521+ {
4522+ savepoint.clone()
4523+ } else {
4524+ doc.savepoint(view)
41764525 };
41774526
4178− let offset_encoding = language_server.offset_encoding();
4179− let text = doc.text().slice(..);
4180− let cursor = doc.selection(view.id).primary().cursor(text);
4181−
4182− let pos = pos_to_lsp_pos(doc.text(), cursor, offset_encoding);
4527+ let text = savepoint.text.clone();
4528+ let cursor = savepoint.cursor();
4529+
4530+ let mut seen_language_servers = HashSet::new();
4531+
4532+ let mut futures: FuturesUnordered<_> = doc
4533+ .language_servers_with_feature(LanguageServerFeature::Completion)
4534+ .filter(|ls| seen_language_servers.insert(ls.id()))
4535+ .map(|language_server| {
4536+ let language_server_id = language_server.id();
4537+ let offset_encoding = language_server.offset_encoding();
4538+ let pos = pos_to_lsp_pos(&text, cursor, offset_encoding);
4539+ let doc_id = doc.identifier();
4540+ let completion_request = language_server.completion(doc_id, pos, None).unwrap();
4541+
4542+ async move {
4543+ let json = completion_request.await?;
4544+ let response: Option<lsp::CompletionResponse> = serde_json::from_value(json)?;
4545+
4546+ let items = match response {
4547+ Some(lsp::CompletionResponse::Array(items)) => items,
4548+ // TODO: do something with is_incomplete
4549+ Some(lsp::CompletionResponse::List(lsp::CompletionList {
4550+ is_incomplete: _is_incomplete,
4551+ items,
4552+ })) => items,
4553+ None => Vec::new(),
4554+ }
4555+ .into_iter()
4556+ .map(|item| CompletionItem {
4557+ item,
4558+ language_server_id,
4559+ resolved: false,
4560+ })
4561+ .collect();
41834562
4184− let future = match language_server.completion(doc.identifier(), pos, None) {
4185− Some(future) => future,
4186− None => return,
4187− };
4563+ anyhow::Ok(items)
4564+ }
4565+ })
4566+ .collect();
41884567
4189− // setup a chanel that allows the request to be canceled
4568+ // setup a channel that allows the request to be canceled
41904569 let (tx, rx) = oneshot::channel();
41914570 // set completion_request so that this request can be canceled
41924571 // by setting completion_request, the old channel stored there is dropped
41934572 // and the associated request is automatically dropped
41944573 cx.editor.completion_request_handle = Some(tx);
41954574 let future = async move {
4575+ let items_future = async move {
4576+ let mut items = Vec::new();
4577+ // TODO if one completion request errors, all other completion requests are discarded (even if they're valid)
4578+ while let Some(mut lsp_items) = futures.try_next().await? {
4579+ items.append(&mut lsp_items);
4580+ }
4581+ anyhow::Ok(items)
4582+ };
41964583 tokio::select! {
41974584 biased;
41984585 _ = rx => {
4199− Ok(serde_json::Value::Null)
4586+ Ok(Vec::new())
42004587 }
4201− res = future => {
4588+ res = items_future => {
42024589 res
42034590 }
42044591 }
@@ -4214,7 +4601,6 @@ pub fn completion(cx: &mut Context) {
42144601 iter.reverse();
42154602 let offset = iter.take_while(|ch| chars::char_is_word(*ch)).count();
42164603 let start_offset = cursor.saturating_sub(offset);
4217− let savepoint = doc.savepoint(view);
42184604
42194605 let trigger_doc = doc.id();
42204606 let trigger_view = view.id;
@@ -4233,46 +4619,45 @@ pub fn completion(cx: &mut Context) {
42334619 },
42344620 ));
42354621
4236− cx.callback(
4237− future,
4238− move |editor, compositor, response: Option<lsp::CompletionResponse>| {
4622+ cx.jobs.callback(async move {
4623+ let items = future.await?;
4624+ let call = move |editor: &mut Editor, compositor: &mut Compositor| {
42394625 let (view, doc) = current_ref!(editor);
42404626 // check if the completion request is stale.
42414627 //
4242− // Completions are completed asynchrounsly and therefore the user could
4628+ // Completions are completed asynchronously and therefore the user could
42434629 //switch document/view or leave insert mode. In all of thoise cases the
42444630 // completion should be discarded
42454631 if editor.mode != Mode::Insert || view.id != trigger_view || doc.id() != trigger_doc {
42464632 return;
42474633 }
42484634
4249− let items = match response {
4250− Some(lsp::CompletionResponse::Array(items)) => items,
4251− // TODO: do something with is_incomplete
4252− Some(lsp::CompletionResponse::List(lsp::CompletionList {
4253− is_incomplete: _is_incomplete,
4254− items,
4255− })) => items,
4256− None => Vec::new(),
4257− };
4258−
42594635 if items.is_empty() {
42604636 // editor.set_error("No completion available");
42614637 return;
42624638 }
42634639 let size = compositor.size();
42644640 let ui = compositor.find::<ui::EditorView>().unwrap();
4265− ui.set_completion(
4641+ let completion_area = ui.set_completion(
42664642 editor,
42674643 savepoint,
42684644 items,
4269− offset_encoding,
42704645 start_offset,
42714646 trigger_offset,
42724647 size,
42734648 );
4274− },
4275− );
4649+ let size = compositor.size();
4650+ let signature_help_area = compositor
4651+ .find_id::<Popup<SignatureHelp>>(SignatureHelp::ID)
4652+ .map(|signature_help| signature_help.area(size, editor));
4653+ // Delete the signature help popup if they intersect.
4654+ if matches!((completion_area, signature_help_area),(Some(a), Some(b)) if a.intersects(b))
4655+ {
4656+ compositor.remove(SignatureHelp::ID);
4657+ }
4658+ };
4659+ Ok(Callback::EditorCompositor(Box::new(call)))
4660+ });
42764661}
42774662
42784663// comments
@@ -4307,7 +4692,13 @@ fn rotate_selections_backward(cx: &mut Context) {
43074692 rotate_selections(cx, Direction::Backward)
43084693}
43094694
4310−fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
4695+enum ReorderStrategy {
4696+ RotateForward,
4697+ RotateBackward,
4698+ Reverse,
4699+}
4700+
4701+fn reorder_selection_contents(cx: &mut Context, strategy: ReorderStrategy) {
43114702 let count = cx.count;
43124703 let (view, doc) = current!(cx.editor);
43134704 let text = doc.text().slice(..);
@@ -4325,9 +4716,10 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
43254716
43264717 for chunk in fragments.chunks_mut(group) {
43274718 // TODO: also modify main index
4328− match direction {
4329− Direction::Forward => chunk.rotate_right(1),
4330− Direction::Backward => chunk.rotate_left(1),
4719+ match strategy {
4720+ ReorderStrategy::RotateForward => chunk.rotate_right(1),
4721+ ReorderStrategy::RotateBackward => chunk.rotate_left(1),
4722+ ReorderStrategy::Reverse => chunk.reverse(),
43314723 };
43324724 }
43334725
@@ -4344,10 +4736,13 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
43444736}
43454737
43464738fn rotate_selection_contents_forward(cx: &mut Context) {
4347− rotate_selection_contents(cx, Direction::Forward)
4739+ reorder_selection_contents(cx, ReorderStrategy::RotateForward)
43484740}
43494741fn rotate_selection_contents_backward(cx: &mut Context) {
4350− rotate_selection_contents(cx, Direction::Backward)
4742+ reorder_selection_contents(cx, ReorderStrategy::RotateBackward)
4743+}
4744+fn reverse_selection_contents(cx: &mut Context) {
4745+ reorder_selection_contents(cx, ReorderStrategy::Reverse)
43514746}
43524747
43534748// tree sitter node selection
@@ -4371,8 +4766,7 @@ fn expand_selection(cx: &mut Context) {
43714766 }
43724767 }
43734768 };
4374− motion(cx.editor);
4375− cx.editor.last_motion = Some(Motion(Box::new(motion)));
4769+ cx.editor.apply_motion(motion);
43764770}
43774771
43784772fn shrink_selection(cx: &mut Context) {
@@ -4396,8 +4790,7 @@ fn shrink_selection(cx: &mut Context) {
43964790 doc.set_selection(view.id, selection);
43974791 }
43984792 };
4399− motion(cx.editor);
4400− cx.editor.last_motion = Some(Motion(Box::new(motion)));
4793+ cx.editor.apply_motion(motion);
44014794}
44024795
44034796fn select_sibling_impl<F>(cx: &mut Context, sibling_fn: &'static F)
@@ -4415,8 +4808,7 @@ where
44154808 doc.set_selection(view.id, selection);
44164809 }
44174810 };
4418− motion(cx.editor);
4419− cx.editor.last_motion = Some(Motion(Box::new(motion)));
4811+ cx.editor.apply_motion(motion);
44204812}
44214813
44224814fn select_next_sibling(cx: &mut Context) {
@@ -4427,22 +4819,68 @@ fn select_prev_sibling(cx: &mut Context) {
44274819 select_sibling_impl(cx, &|node| Node::prev_sibling(&node))
44284820}
44294821
4822+fn move_node_bound_impl(cx: &mut Context, dir: Direction, movement: Movement) {
4823+ let motion = move |editor: &mut Editor| {
4824+ let (view, doc) = current!(editor);
4825+
4826+ if let Some(syntax) = doc.syntax() {
4827+ let text = doc.text().slice(..);
4828+ let current_selection = doc.selection(view.id);
4829+
4830+ let selection = movement::move_parent_node_end(
4831+ syntax,
4832+ text,
4833+ current_selection.clone(),
4834+ dir,
4835+ movement,
4836+ );
4837+
4838+ doc.set_selection(view.id, selection);
4839+
4840+ // [TODO] temporary workaround until we're not using the idle timer to
4841+ // trigger auto completions any more
4842+ editor.clear_idle_timer();
4843+ }
4844+ };
4845+
4846+ cx.editor.apply_motion(motion);
4847+}
4848+
4849+pub fn move_parent_node_end(cx: &mut Context) {
4850+ move_node_bound_impl(cx, Direction::Forward, Movement::Move)
4851+}
4852+
4853+pub fn move_parent_node_start(cx: &mut Context) {
4854+ move_node_bound_impl(cx, Direction::Backward, Movement::Move)
4855+}
4856+
4857+pub fn extend_parent_node_end(cx: &mut Context) {
4858+ move_node_bound_impl(cx, Direction::Forward, Movement::Extend)
4859+}
4860+
4861+pub fn extend_parent_node_start(cx: &mut Context) {
4862+ move_node_bound_impl(cx, Direction::Backward, Movement::Extend)
4863+}
4864+
44304865fn match_brackets(cx: &mut Context) {
44314866 let (view, doc) = current!(cx.editor);
4867+ let is_select = cx.editor.mode == Mode::Select;
4868+ let text = doc.text();
4869+ let text_slice = text.slice(..);
44324870
4433− if let Some(syntax) = doc.syntax() {
4434− let text = doc.text().slice(..);
4435− let selection = doc.selection(view.id).clone().transform(|range| {
4436− if let Some(pos) =
4437− match_brackets::find_matching_bracket_fuzzy(syntax, doc.text(), range.cursor(text))
4438− {
4439− range.put_cursor(text, pos, cx.editor.mode == Mode::Select)
4440− } else {
4441− range
4442− }
4443− });
4444− doc.set_selection(view.id, selection);
4445− }
4871+ let selection = doc.selection(view.id).clone().transform(|range| {
4872+ let pos = range.cursor(text_slice);
4873+ if let Some(matched_pos) = doc.syntax().map_or_else(
4874+ || match_brackets::find_matching_bracket_plaintext(text.slice(..), pos),
4875+ |syntax| match_brackets::find_matching_bracket_fuzzy(syntax, text.slice(..), pos),
4876+ ) {
4877+ range.put_cursor(text_slice, matched_pos, is_select)
4878+ } else {
4879+ range
4880+ }
4881+ });
4882+
4883+ doc.set_selection(view.id, selection);
44464884}
44474885
44484886//
@@ -4537,17 +4975,19 @@ fn transpose_view(cx: &mut Context) {
45374975 cx.editor.transpose_view()
45384976}
45394977
4540−// split helper, clear it later
4541−fn split(cx: &mut Context, action: Action) {
4542− let (view, doc) = current!(cx.editor);
4978+/// Open a new split in the given direction specified by the action.
4979+///
4980+/// Maintain the current view (both the cursor's position and view in document).
4981+fn split(editor: &mut Editor, action: Action) {
4982+ let (view, doc) = current!(editor);
45434983 let id = doc.id();
45444984 let selection = doc.selection(view.id).clone();
45454985 let offset = view.offset;
45464986
4547− cx.editor.switch(id, action);
4987+ editor.switch(id, action);
45484988
45494989 // match the selection in the previous view
4550− let (view, doc) = current!(cx.editor);
4990+ let (view, doc) = current!(editor);
45514991 doc.set_selection(view.id, selection);
45524992 // match the view scroll offset (switch doesn't handle this fully
45534993 // since the selection is only matched after the split)
@@ -4555,7 +4995,7 @@ fn split(cx: &mut Context, action: Action) {
45554995}
45564996
45574997fn hsplit(cx: &mut Context) {
4558− split(cx, Action::HorizontalSplit);
4998+ split(cx.editor, Action::HorizontalSplit);
45594999}
45605000
45615001fn hsplit_new(cx: &mut Context) {
@@ -4563,7 +5003,7 @@ fn hsplit_new(cx: &mut Context) {
45635003}
45645004
45655005fn vsplit(cx: &mut Context) {
4566− split(cx, Action::VerticalSplit);
5006+ split(cx.editor, Action::VerticalSplit);
45675007}
45685008
45695009fn vsplit_new(cx: &mut Context) {
@@ -4612,7 +5052,12 @@ fn insert_register(cx: &mut Context) {
46125052 if let Some(ch) = event.char() {
46135053 cx.editor.autoinfo = None;
46145054 cx.register = Some(ch);
4615− paste(cx, Paste::Cursor);
5055+ paste(
5056+ cx.editor,
5057+ cx.register.unwrap_or('"'),
5058+ Paste::Cursor,
5059+ cx.count(),
5060+ );
46165061 }
46175062 })
46185063}
@@ -4696,8 +5141,7 @@ fn goto_ts_object_impl(cx: &mut Context, object: &'static str, direction: Direct
46965141 editor.set_status("Syntax-tree is not available in current buffer");
46975142 }
46985143 };
4699− motion(cx.editor);
4700− cx.editor.last_motion = Some(Motion(Box::new(motion)));
5144+ cx.editor.apply_motion(motion);
47015145}
47025146
47035147fn goto_next_function(cx: &mut Context) {
@@ -4818,8 +5262,7 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
48185262 });
48195263 doc.set_selection(view.id, selection);
48205264 };
4821− textobject(cx.editor);
4822− cx.editor.last_motion = Some(Motion(Box::new(textobject)));
5265+ cx.editor.apply_motion(textobject);
48235266 }
48245267 });
48255268
@@ -4909,11 +5352,18 @@ fn surround_replace(cx: &mut Context) {
49095352 }
49105353 };
49115354
5355+ let selection = selection.clone();
5356+ let ranges: SmallVec<[Range; 1]> = change_pos.iter().map(|&p| Range::point(p)).collect();
5357+ doc.set_selection(
5358+ view.id,
5359+ Selection::new(ranges, selection.primary_index() * 2),
5360+ );
5361+
49125362 cx.on_next_key(move |cx, event| {
49135363 let (view, doc) = current!(cx.editor);
49145364 let to = match event.char() {
49155365 Some(to) => to,
4916− None => return,
5366+ None => return doc.set_selection(view.id, selection),
49175367 };
49185368 let (open, close) = surround::get_pair(to);
49195369 let transaction = Transaction::change(
@@ -4924,6 +5374,7 @@ fn surround_replace(cx: &mut Context) {
49245374 (pos, pos + 1, Some(t))
49255375 }),
49265376 );
5377+ doc.set_selection(view.id, selection);
49275378 doc.apply(&transaction, view.id);
49285379 exit_select_mode(cx);
49295380 });
@@ -5069,9 +5520,10 @@ async fn shell_impl_async(
50695520 let output = if let Some(mut stdin) = process.stdin.take() {
50705521 let input_task = tokio::spawn(async move {
50715522 if let Some(input) = input {
5072− helix_view::document::to_writer(&mut stdin, encoding::UTF_8, &input).await?;
5523+ helix_view::document::to_writer(&mut stdin, (encoding::UTF_8, false), &input)
5524+ .await?;
50735525 }
5074− Ok::<_, anyhow::Error>(())
5526+ anyhow::Ok(())
50755527 });
50765528 let (output, _) = tokio::join! {
50775529 process.wait_with_output(),
@@ -5157,12 +5609,18 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
51575609 };
51585610
51595611 // These `usize`s cannot underflow because selection ranges cannot overlap.
5160− // Once the MSRV is 1.66.0 (mixed_integer_ops is stabilized), we can use checked
5161− // arithmetic to assert this.
5162− let anchor = (to as isize + offset - deleted_len as isize) as usize;
5612+ let anchor = to
5613+ .checked_add_signed(offset)
5614+ .expect("Selection ranges cannot overlap")
5615+ .checked_sub(deleted_len)
5616+ .expect("Selection ranges cannot overlap");
51635617 let new_range = Range::new(anchor, anchor + output_len).with_direction(range.direction());
51645618 ranges.push(new_range);
5165− offset = offset + output_len as isize - deleted_len as isize;
5619+ offset = offset
5620+ .checked_add_unsigned(output_len)
5621+ .expect("Selection ranges cannot overlap")
5622+ .checked_sub_unsigned(deleted_len)
5623+ .expect("Selection ranges cannot overlap");
51665624
51675625 changes.push((from, to, Some(output)));
51685626 }
@@ -5184,7 +5642,7 @@ fn shell_prompt(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBeha
51845642 cx,
51855643 prompt,
51865644 Some('|'),
5187− ui::completers::none,
5645+ ui::completers::filename,
51885646 move |cx, input: &str, event: PromptEvent| {
51895647 if event != PromptEvent::Validate {
51905648 return;
@@ -5301,6 +5759,7 @@ fn increment_impl(cx: &mut Context, increment_direction: IncrementDirection) {
53015759 let transaction = Transaction::change(doc.text(), changes.into_iter());
53025760 let transaction = transaction.with_selection(new_selection);
53035761 doc.apply(&transaction, view.id);
5762+ exit_select_mode(cx);
53045763 }
53055764}
53065765
@@ -5319,9 +5778,12 @@ fn record_macro(cx: &mut Context) {
53195778 }
53205779 })
53215780 .collect::<String>();
5322− cx.editor.registers.write(reg, vec![s]);
5323− cx.editor
5324− .set_status(format!("Recorded to register [{}]", reg));
5781+ match cx.editor.registers.write(reg, vec![s]) {
5782+ Ok(_) => cx
5783+ .editor
5784+ .set_status(format!("Recorded to register [{}]", reg)),
5785+ Err(err) => cx.editor.set_error(err.to_string()),
5786+ }
53255787 } else {
53265788 let reg = cx.register.take().unwrap_or('@');
53275789 cx.editor.macro_recording = Some((reg, Vec::new()));
@@ -5341,8 +5803,14 @@ fn replay_macro(cx: &mut Context) {
53415803 return;
53425804 }
53435805
5344− let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) {
5345− match helix_view::input::parse_macro(keys_str) {
5806+ let keys: Vec<KeyEvent> = if let Some(keys) = cx
5807+ .editor
5808+ .registers
5809+ .read(reg, cx.editor)
5810+ .filter(|values| values.len() == 1)
5811+ .map(|mut values| values.next().unwrap())
5812+ {
5813+ match helix_view::input::parse_macro(&keys) {
53465814 Ok(keys) => keys,
53475815 Err(err) => {
53485816 cx.editor.set_error(format!("Invalid macro: {}", err));