@@ -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_workspace, graphemes,
15+ encoding, find_first_non_whitespace_char, find_root, 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,
2726 text_annotations::TextAnnotations,
2827 textobject,
2928 tree_sitter::Node,
3029 unicode::width::UnicodeWidthChar,
31− visual_offset_from_block, Deletion, LineEnding, Position, Range, Rope, RopeGraphemes,
32− RopeReader, RopeSlice, Selection, SmallVec, Tendril, Transaction,
30+ visual_offset_from_block, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice,
31+ Selection, SmallVec, Tendril, Transaction,
3332};
3433use helix_view::{
34+ clipboard::ClipboardType,
3535 document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
36− editor::{Action, CompleteAction},
36+ editor::{Action, Motion},
3737 info::Info,
3838 input::KeyEvent,
3939 keyboard::KeyCode,
@@ -43,6 +43,7 @@ use helix_view::{
4343};
4444
4545use anyhow::{anyhow, bail, ensure, Context as _};
46+use fuzzy_matcher::FuzzyMatcher;
4647use insert::*;
4748use movement::Movement;
4849
@@ -53,20 +54,15 @@ use crate::{
5354 job::Callback,
5455 keymap::ReverseKeymap,
5556 ui::{
56− self, editor::InsertEvent, lsp::SignatureHelp, overlay::overlaid, CompletionItem, Picker,
57− Popup, Prompt, PromptEvent,
57+ self, editor::InsertEvent, overlay::overlayed, FilePicker, Picker, Popup, Prompt,
58+ PromptEvent,
5859 },
5960};
6061
6162use crate::job::{self, Jobs};
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−};
63+use futures_util::StreamExt;
64+use std::{collections::HashMap, fmt, future::Future};
65+use std::{collections::HashSet, num::NonZeroUsize};
7066
7167use std::{
7268 borrow::Cow,
@@ -75,11 +71,11 @@ use std::{
7571
7672use once_cell::sync::Lazy;
7773use serde::de::{self, Deserialize, Deserializer};
78−use url::Url;
7974
8075use grep_regex::RegexMatcherBuilder;
8176use grep_searcher::{sinks, BinaryDetection, SearcherBuilder};
8277use ignore::{DirEntry, WalkBuilder, WalkState};
78+use tokio_stream::wrappers::UnboundedReceiverStream;
8379
8480pub type OnKeyCallback = Box<dyn FnOnce(&mut Context, KeyEvent)>;
8581
@@ -101,13 +97,6 @@ impl<'a> Context<'a> {
10197 }));
10298 }
10399
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−
111100 #[inline]
112101 pub fn on_next_key(
113102 &mut self,
@@ -250,9 +239,6 @@ impl MappableCommand {
250239 move_next_long_word_start, "Move to start of next long word",
251240 move_prev_long_word_start, "Move to start of previous long word",
252241 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",
256242 extend_next_word_start, "Extend to start of next word",
257243 extend_prev_word_start, "Extend to start of previous word",
258244 extend_next_word_end, "Extend to end of next word",
@@ -260,9 +246,6 @@ impl MappableCommand {
260246 extend_next_long_word_start, "Extend to start of next long word",
261247 extend_prev_long_word_start, "Extend to start of previous long word",
262248 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",
266249 find_till_char, "Move till next occurrence of char",
267250 find_next_char, "Move to next occurrence of char",
268251 extend_till_char, "Extend till next occurrence of char",
@@ -284,7 +267,6 @@ impl MappableCommand {
284267 select_regex, "Select all regex matches inside selections",
285268 split_selection, "Split selections on regex matches",
286269 split_selection_on_newline, "Split selection on newlines",
287− merge_selections, "Merge selections",
288270 merge_consecutive_selections, "Merge consecutive selections",
289271 search, "Search for regex pattern",
290272 rsearch, "Reverse search for regex pattern",
@@ -337,7 +319,7 @@ impl MappableCommand {
337319 goto_implementation, "Goto implementation",
338320 goto_file_start, "Goto line number <n> else file start",
339321 goto_file_end, "Goto file end",
340− goto_file, "Goto files/URLs in selection",
322+ goto_file, "Goto files in selection",
341323 goto_file_hsplit, "Goto files in selection (hsplit)",
342324 goto_file_vsplit, "Goto files in selection (vsplit)",
343325 goto_reference, "Goto references",
@@ -365,11 +347,9 @@ impl MappableCommand {
365347 goto_first_nonwhitespace, "Goto first non-blank in line",
366348 trim_selections, "Trim whitespace from selections",
367349 extend_to_line_start, "Extend to line start",
368− extend_to_first_nonwhitespace, "Extend to first non-blank in line",
369350 extend_to_line_end, "Extend to line end",
370351 extend_to_line_end_newline, "Extend to line end",
371352 signature_help, "Show signature help",
372− smart_tab, "Insert tab if all cursors have all whitespace to their left; otherwise, run a separate command.",
373353 insert_tab, "Insert tab char",
374354 insert_newline, "Insert newline char",
375355 delete_char_backward, "Delete previous char",
@@ -384,9 +364,6 @@ impl MappableCommand {
384364 later, "Move forward in history",
385365 commit_undo_checkpoint, "Commit changes to new checkpoint",
386366 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",
390367 yank_joined_to_clipboard, "Join and yank selections to clipboard",
391368 yank_main_selection_to_clipboard, "Yank main selection to clipboard",
392369 yank_joined_to_primary_clipboard, "Join and yank selections to primary clipboard",
@@ -417,7 +394,6 @@ impl MappableCommand {
417394 rotate_selections_backward, "Rotate selections backward",
418395 rotate_selection_contents_forward, "Rotate selection contents forward",
419396 rotate_selection_contents_backward, "Rotate selections contents backward",
420− reverse_selection_contents, "Reverse selections contents",
421397 expand_selection, "Expand selection to parent syntax node",
422398 shrink_selection, "Shrink selection to previously expanded syntax node",
423399 select_next_sibling, "Select next sibling in syntax tree",
@@ -525,7 +501,7 @@ impl std::str::FromStr for MappableCommand {
525501
526502 fn from_str(s: &str) -> Result<Self, Self::Err> {
527503 if let Some(suffix) = s.strip_prefix(':') {
528− let mut typable_command = suffix.split(' ').map(|arg| arg.trim());
504+ let mut typable_command = suffix.split(' ').into_iter().map(|arg| arg.trim());
529505 let name = typable_command
530506 .next()
531507 .ok_or_else(|| anyhow!("Expected typable command name"))?;
@@ -811,72 +787,58 @@ fn extend_to_line_start(cx: &mut Context) {
811787}
812788
813789fn kill_to_line_start(cx: &mut Context) {
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− }
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
831804 } else {
832805 // select until start of line
833806 first_char
834− };
835− (head, anchor)
836− },
837− Direction::Backward,
838− );
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);
839817}
840818
841819fn kill_to_line_end(cx: &mut Context) {
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);
820+ let (view, doc) = current!(cx.editor);
821+ let text = doc.text().slice(..);
848822
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−}
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);
859827
860−fn goto_first_nonwhitespace(cx: &mut Context) {
861− let (view, doc) = current!(cx.editor);
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);
862836
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− )
837+ lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
872838}
873839
874−fn extend_to_first_nonwhitespace(cx: &mut Context) {
840+fn goto_first_nonwhitespace(cx: &mut Context) {
875841 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) {
880842 let text = doc.text().slice(..);
881843
882844 let selection = doc.selection(view.id).clone().transform(|range| {
@@ -884,7 +846,7 @@ fn goto_first_nonwhitespace_impl(view: &mut View, doc: &mut Document, movement:
884846
885847 if let Some(pos) = find_first_non_whitespace_char(text.line(line)) {
886848 let pos = pos + text.line_to_char(line);
887− range.put_cursor(text, pos, movement == Movement::Extend)
849+ range.put_cursor(text, pos, cx.editor.mode == Mode::Select)
888850 } else {
889851 range
890852 }
@@ -990,7 +952,6 @@ fn align_selections(cx: &mut Context) {
990952
991953 let transaction = Transaction::change(doc.text(), changes.into_iter());
992954 doc.apply(&transaction, view.id);
993− exit_select_mode(cx);
994955}
995956
996957fn goto_window(cx: &mut Context, align: Align) {
@@ -1082,10 +1043,6 @@ fn move_prev_long_word_start(cx: &mut Context) {
10821043 move_word_impl(cx, movement::move_prev_long_word_start)
10831044}
10841045
1085−fn move_prev_long_word_end(cx: &mut Context) {
1086− move_word_impl(cx, movement::move_prev_long_word_end)
1087−}
1088−
10891046fn move_next_long_word_end(cx: &mut Context) {
10901047 move_word_impl(cx, movement::move_next_long_word_end)
10911048}
@@ -1110,7 +1067,8 @@ where
11101067 .transform(|range| move_fn(text, range, count, behavior));
11111068 doc.set_selection(view.id, selection);
11121069 };
1113− cx.editor.apply_motion(motion)
1070+ motion(cx.editor);
1071+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
11141072}
11151073
11161074fn goto_prev_paragraph(cx: &mut Context) {
@@ -1165,10 +1123,6 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
11651123 let (view, doc) = current_ref!(cx.editor);
11661124 let text = doc.text();
11671125 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();
11721126 let mut paths: Vec<_> = selections
11731127 .iter()
11741128 .map(|r| text.slice(r.from()..r.to()).to_string())
@@ -1196,80 +1150,16 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
11961150 .to_string(),
11971151 );
11981152 }
1199−
12001153 for sel in paths {
12011154 let p = sel.trim();
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) {
1155+ if !p.is_empty() {
1156+ if let Err(e) = cx.editor.open(&PathBuf::from(p), action) {
12501157 cx.editor.set_error(format!("Open file failed: {:?}", e));
12511158 }
12521159 }
12531160 }
12541161}
12551162
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−
12731163fn extend_word_impl<F>(cx: &mut Context, extend_fn: F)
12741164where
12751165 F: Fn(RopeSlice, Range, usize) -> Range,
@@ -1310,74 +1200,14 @@ fn extend_prev_long_word_start(cx: &mut Context) {
13101200 extend_word_impl(cx, movement::move_prev_long_word_start)
13111201}
13121202
1313−fn extend_prev_long_word_end(cx: &mut Context) {
1314− extend_word_impl(cx, movement::move_prev_long_word_end)
1315−}
1316−
13171203fn extend_next_long_word_end(cx: &mut Context) {
13181204 extend_word_impl(cx, movement::move_next_long_word_end)
13191205}
13201206
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) {
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+{
13811211 // TODO: count is reset to 1 before next key so we move it into the closure here.
13821212 // Would be nice to carry over.
13831213 let count = cx.count();
@@ -1390,9 +1220,13 @@ fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bo
13901220 KeyEvent {
13911221 code: KeyCode::Enter,
13921222 ..
1393− } => {
1394− find_char_line_ending(cx, count, direction, inclusive, extend);
1395− return;
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()
13961230 }
13971231
13981232 KeyEvent {
@@ -1405,18 +1239,11 @@ fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bo
14051239 } => ch,
14061240 _ => return,
14071241 };
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− };
14181242
1419− cx.editor.apply_motion(motion);
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+ })));
14201247 })
14211248}
14221249
@@ -1495,39 +1322,46 @@ fn find_prev_char_impl(
14951322}
14961323
14971324fn find_till_char(cx: &mut Context) {
1498− find_char(cx, Direction::Forward, false, false);
1325+ will_find_char(cx, find_next_char_impl, false, false)
14991326}
15001327
15011328fn find_next_char(cx: &mut Context) {
1502− find_char(cx, Direction::Forward, true, false)
1329+ will_find_char(cx, find_next_char_impl, true, false)
15031330}
15041331
15051332fn extend_till_char(cx: &mut Context) {
1506− find_char(cx, Direction::Forward, false, true)
1333+ will_find_char(cx, find_next_char_impl, false, true)
15071334}
15081335
15091336fn extend_next_char(cx: &mut Context) {
1510− find_char(cx, Direction::Forward, true, true)
1337+ will_find_char(cx, find_next_char_impl, true, true)
15111338}
15121339
15131340fn till_prev_char(cx: &mut Context) {
1514− find_char(cx, Direction::Backward, false, false)
1341+ will_find_char(cx, find_prev_char_impl, false, false)
15151342}
15161343
15171344fn find_prev_char(cx: &mut Context) {
1518− find_char(cx, Direction::Backward, true, false)
1345+ will_find_char(cx, find_prev_char_impl, true, false)
15191346}
15201347
15211348fn extend_till_prev_char(cx: &mut Context) {
1522− find_char(cx, Direction::Backward, false, true)
1349+ will_find_char(cx, find_prev_char_impl, false, true)
15231350}
15241351
15251352fn extend_prev_char(cx: &mut Context) {
1526− find_char(cx, Direction::Backward, true, true)
1353+ will_find_char(cx, find_prev_char_impl, true, true)
15271354}
15281355
15291356fn repeat_last_motion(cx: &mut Context) {
1530− cx.editor.repeat_last_motion(cx.count())
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+ }
15311365}
15321366
15331367fn replace(cx: &mut Context) {
@@ -1594,7 +1428,6 @@ where
15941428 });
15951429
15961430 doc.apply(&transaction, view.id);
1597− exit_select_mode(cx);
15981431}
15991432
16001433fn switch_case(cx: &mut Context) {
@@ -1637,7 +1470,7 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
16371470 let cursor = range.cursor(text);
16381471 let height = view.inner_height();
16391472
1640− let scrolloff = config.scrolloff.min(height.saturating_sub(1) / 2);
1473+ let scrolloff = config.scrolloff.min(height / 2);
16411474 let offset = match direction {
16421475 Forward => offset as isize,
16431476 Backward => -(offset as isize),
@@ -1656,19 +1489,18 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
16561489 &annotations,
16571490 );
16581491
1659− let mut head;
1492+ let head;
16601493 match direction {
16611494 Forward => {
1662− let off;
1663− (head, off) = char_idx_at_visual_offset(
1495+ head = char_idx_at_visual_offset(
16641496 doc_text,
16651497 view.offset.anchor,
16661498 (view.offset.vertical_offset + scrolloff) as isize,
16671499 0,
16681500 &text_fmt,
16691501 &annotations,
1670− );
1671− head += (off != 0) as usize;
1502+ )
1503+ .0;
16721504 if head <= cursor {
16731505 return;
16741506 }
@@ -1677,7 +1509,7 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
16771509 head = char_idx_at_visual_offset(
16781510 doc_text,
16791511 view.offset.anchor,
1680− (view.offset.vertical_offset + height - scrolloff - 1) as isize,
1512+ (view.offset.vertical_offset + height - scrolloff) as isize,
16811513 0,
16821514 &text_fmt,
16831515 &annotations,
@@ -1728,7 +1560,7 @@ fn half_page_down(cx: &mut Context) {
17281560}
17291561
17301562#[allow(deprecated)]
1731−// currently uses the deprecated `visual_coords_at_pos`/`pos_at_visual_coords` functions
1563+// currently uses the deprected `visual_coords_at_pos`/`pos_at_visual_coords` functions
17321564// as this function ignores softwrapping (and virtual text) and instead only cares
17331565// about "text visual position"
17341566//
@@ -1836,8 +1668,8 @@ fn select_regex(cx: &mut Context) {
18361668 "select:".into(),
18371669 Some(reg),
18381670 ui::completers::none,
1839− move |cx, regex, event| {
1840− let (view, doc) = current!(cx.editor);
1671+ move |editor, regex, event| {
1672+ let (view, doc) = current!(editor);
18411673 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
18421674 return;
18431675 }
@@ -1858,8 +1690,8 @@ fn split_selection(cx: &mut Context) {
18581690 "split:".into(),
18591691 Some(reg),
18601692 ui::completers::none,
1861− move |cx, regex, event| {
1862− let (view, doc) = current!(cx.editor);
1693+ move |editor, regex, event| {
1694+ let (view, doc) = current!(editor);
18631695 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
18641696 return;
18651697 }
@@ -1881,12 +1713,6 @@ fn split_selection_on_newline(cx: &mut Context) {
18811713 doc.set_selection(view.id, selection);
18821714}
18831715
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−
18901716fn merge_consecutive_selections(cx: &mut Context) {
18911717 let (view, doc) = current!(cx.editor);
18921718 let selection = doc.selection(view.id).clone().merge_consecutive_ranges();
@@ -1982,11 +1808,11 @@ fn search_impl(
19821808
19831809fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
19841810 let mut items = reg
1985− .and_then(|reg| cx.editor.registers.read(reg, cx.editor))
1986− .map_or(Vec::new(), |reg| reg.take(200).collect());
1811+ .and_then(|reg| cx.editor.registers.get(reg))
1812+ .map_or(Vec::new(), |reg| reg.read().iter().take(200).collect());
19871813 items.sort_unstable();
19881814 items.dedup();
1989− items.into_iter().map(|value| value.to_string()).collect()
1815+ items.into_iter().cloned().collect()
19901816}
19911817
19921818fn search(cx: &mut Context) {
@@ -2023,14 +1849,12 @@ fn searcher(cx: &mut Context, direction: Direction) {
20231849 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
20241850 .collect()
20251851 },
2026− move |cx, regex, event| {
2027− if event == PromptEvent::Validate {
2028− cx.editor.registers.last_search_register = reg;
2029− } else if event != PromptEvent::Update {
1852+ move |editor, regex, event| {
1853+ if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
20301854 return;
20311855 }
20321856 search_impl(
2033− cx.editor,
1857+ editor,
20341858 &contents,
20351859 ®ex,
20361860 Movement::Move,
@@ -2045,13 +1869,11 @@ fn searcher(cx: &mut Context, direction: Direction) {
20451869
20461870fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) {
20471871 let count = cx.count();
2048− let register = cx
2049− .register
2050− .unwrap_or(cx.editor.registers.last_search_register);
20511872 let config = cx.editor.config();
20521873 let scrolloff = config.scrolloff;
2053− if let Some(query) = cx.editor.registers.first(register, cx.editor) {
2054− let doc = doc!(cx.editor);
1874+ let (_, doc) = current!(cx.editor);
1875+ let registers = &cx.editor.registers;
1876+ if let Some(query) = registers.read('/').and_then(|query| query.last()) {
20551877 let contents = doc.text().slice(..).to_string();
20561878 let search_config = &config.search;
20571879 let case_insensitive = if search_config.smart_case {
@@ -2060,7 +1882,7 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
20601882 false
20611883 };
20621884 let wrap_around = search_config.wrap_around;
2063− if let Ok(regex) = RegexBuilder::new(&query)
1885+ if let Ok(regex) = RegexBuilder::new(query)
20641886 .case_insensitive(case_insensitive)
20651887 .multi_line(true)
20661888 .build()
@@ -2100,7 +1922,6 @@ fn extend_search_prev(cx: &mut Context) {
21001922}
21011923
21021924fn search_selection(cx: &mut Context) {
2103− let register = cx.register.unwrap_or('/');
21041925 let (view, doc) = current!(cx.editor);
21051926 let contents = doc.text().slice(..);
21061927
@@ -2113,24 +1934,13 @@ fn search_selection(cx: &mut Context) {
21131934 .collect::<Vec<_>>()
21141935 .join("|");
21151936
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− }
1937+ let msg = format!("register '{}' set to '{}'", '/', ®ex);
1938+ cx.editor.registers.push('/', regex);
1939+ cx.editor.set_status(msg);
21241940}
21251941
21261942fn make_search_word_bounded(cx: &mut Context) {
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) {
1943+ let regex = match cx.editor.registers.last('/') {
21341944 Some(regex) => regex,
21351945 None => return,
21361946 };
@@ -2148,19 +1958,14 @@ fn make_search_word_bounded(cx: &mut Context) {
21481958 if !start_anchored {
21491959 new_regex.push_str("\\b");
21501960 }
2151− new_regex.push_str(®ex);
1961+ new_regex.push_str(regex);
21521962 if !end_anchored {
21531963 new_regex.push_str("\\b");
21541964 }
21551965
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− }
1966+ let msg = format!("register '{}' set to '{}'", '/', &new_regex);
1967+ cx.editor.registers.push('/', new_regex);
1968+ cx.editor.set_status(msg);
21641969}
21651970
21661971fn global_search(cx: &mut Context) {
@@ -2199,11 +2004,13 @@ fn global_search(cx: &mut Context) {
21992004 }
22002005 }
22012006
2007+ let (all_matches_sx, all_matches_rx) = tokio::sync::mpsc::unbounded_channel::<FileResult>();
22022008 let config = cx.editor.config();
22032009 let smart_case = config.search.smart_case;
22042010 let file_picker_config = config.file_picker.clone();
22052011
22062012 let reg = cx.register.unwrap_or('/');
2013+
22072014 let completions = search_completions(cx, Some(reg));
22082015 ui::regex_prompt(
22092016 cx,
@@ -2216,67 +2023,43 @@ fn global_search(cx: &mut Context) {
22162023 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
22172024 .collect()
22182025 },
2219− move |cx, regex, event| {
2026+ move |_editor, regex, event| {
22202027 if event != PromptEvent::Validate {
22212028 return;
22222029 }
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();
22312030
22322031 if let Ok(matcher) = RegexMatcherBuilder::new()
22332032 .case_smart(smart_case)
22342033 .build(regex.as_str())
22352034 {
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);
2035+ let searcher = SearcherBuilder::new()
2036+ .binary_detection(BinaryDetection::quit(b'\x00'))
2037+ .build();
22442038
2039+ let search_root = std::env::current_dir()
2040+ .expect("Global search error: Failed to get current dir");
22452041 let dedup_symlinks = file_picker_config.deduplicate_links;
22462042 let absolute_root = search_root
22472043 .canonicalize()
22482044 .unwrap_or_else(|_| search_root.clone());
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(|| {
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(|| {
22762060 let mut searcher = searcher.clone();
22772061 let matcher = matcher.clone();
2278− let injector = injector_.clone();
2279− let documents = &documents;
2062+ let all_matches_sx = all_matches_sx.clone();
22802063 Box::new(move |entry: Result<DirEntry, ignore::Error>| -> WalkState {
22812064 let entry = match entry {
22822065 Ok(entry) => entry,
@@ -2289,39 +2072,17 @@ fn global_search(cx: &mut Context) {
22892072 _ => return WalkState::Continue,
22902073 };
22912074
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− };
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+ );
23252086
23262087 if let Err(err) = result {
23272088 log::error!(
@@ -2330,66 +2091,67 @@ fn global_search(cx: &mut Context) {
23302091 err
23312092 );
23322093 }
2333− if stop {
2334− WalkState::Quit
2335− } else {
2336− WalkState::Continue
2337− }
2094+ WalkState::Continue
23382095 })
23392096 });
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− })
23872097 } else {
23882098 // Otherwise do nothing
23892099 // log::warn!("Global Search Invalid Pattern")
23902100 }
23912101 },
23922102 );
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);
23932155}
23942156
23952157enum Extend {
@@ -2512,40 +2274,23 @@ enum Operation {
25122274 Change,
25132275}
25142276
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−
25292277fn delete_selection_impl(cx: &mut Context, op: Operation) {
25302278 let (view, doc) = current!(cx.editor);
25312279
25322280 let selection = doc.selection(view.id);
2533− let only_whole_lines = selection_is_linewise(selection, doc.text());
25342281
25352282 if cx.register != Some('_') {
25362283 // first yank the selection
25372284 let text = doc.text().slice(..);
25382285 let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
25392286 let reg_name = cx.register.unwrap_or('"');
2540− if let Err(err) = cx.editor.registers.write(reg_name, values) {
2541− cx.editor.set_error(err.to_string());
2542− return;
2543− }
2287+ cx.editor.registers.write(reg_name, values);
25442288 };
25452289
25462290 // then delete
2547− let transaction =
2548− Transaction::delete_by_selection(doc.text(), selection, |range| (range.from(), range.to()));
2291+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
2292+ (range.from(), range.to(), None)
2293+ });
25492294 doc.apply(&transaction, view.id);
25502295
25512296 match op {
@@ -2554,59 +2299,17 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) {
25542299 exit_select_mode(cx);
25552300 }
25562301 Operation::Change => {
2557− if only_whole_lines {
2558− open_above(cx);
2559− } else {
2560− enter_insert_mode(cx);
2561− }
2302+ enter_insert_mode(cx);
25622303 }
25632304 }
25642305}
25652306
25662307#[inline]
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− }
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+ });
26082312 doc.apply(&transaction, view.id);
2609− lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
26102313}
26112314
26122315fn delete_selection(cx: &mut Context) {
@@ -2680,10 +2383,6 @@ fn insert_mode(cx: &mut Context) {
26802383 .transform(|range| Range::new(range.to(), range.from()));
26812384
26822385 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();
26872386}
26882387
26892388// inserts at the end of each selection
@@ -2719,13 +2418,11 @@ fn append_mode(cx: &mut Context) {
27192418}
27202419
27212420fn file_picker(cx: &mut Context) {
2722− let root = find_workspace().0;
2723− if !root.exists() {
2724− cx.editor.set_error("Workspace directory does not exist");
2725− return;
2726− }
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, &[]);
27272424 let picker = ui::file_picker(root, &cx.editor.config());
2728− cx.push_layer(Box::new(overlaid(picker)));
2425+ cx.push_layer(Box::new(overlayed(picker)));
27292426}
27302427
27312428fn file_picker_in_current_buffer_directory(cx: &mut Context) {
@@ -2742,18 +2439,12 @@ fn file_picker_in_current_buffer_directory(cx: &mut Context) {
27422439 };
27432440
27442441 let picker = ui::file_picker(path, &cx.editor.config());
2745− cx.push_layer(Box::new(overlaid(picker)));
2442+ cx.push_layer(Box::new(overlayed(picker)));
27462443}
2747−
27482444fn file_picker_in_current_directory(cx: &mut Context) {
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− }
2445+ let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("./"));
27552446 let picker = ui::file_picker(cwd, &cx.editor.config());
2756− cx.push_layer(Box::new(overlaid(picker)));
2447+ cx.push_layer(Box::new(overlayed(picker)));
27572448}
27582449
27592450fn buffer_picker(cx: &mut Context) {
@@ -2764,7 +2455,6 @@ fn buffer_picker(cx: &mut Context) {
27642455 path: Option<PathBuf>,
27652456 is_modified: bool,
27662457 is_current: bool,
2767− focused_at: std::time::Instant,
27682458 }
27692459
27702460 impl ui::menu::Item for BufferMeta {
@@ -2797,32 +2487,29 @@ fn buffer_picker(cx: &mut Context) {
27972487 path: doc.path().cloned(),
27982488 is_modified: doc.is_modified(),
27992489 is_current: doc.id() == current,
2800− focused_at: doc.focused_at,
28012490 };
28022491
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)));
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)));
28262513}
28272514
28282515fn jumplist_picker(cx: &mut Context) {
@@ -2861,13 +2548,6 @@ fn jumplist_picker(cx: &mut Context) {
28612548 }
28622549 }
28632550
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−
28712551 let new_meta = |view: &View, doc_id: DocumentId, selection: Selection| {
28722552 let doc = &cx.editor.documents.get(&doc_id);
28732553 let text = doc.map_or("".into(), |d| {
@@ -2887,7 +2567,7 @@ fn jumplist_picker(cx: &mut Context) {
28872567 }
28882568 };
28892569
2890− let picker = Picker::new(
2570+ let picker = FilePicker::new(
28912571 cx.editor
28922572 .tree
28932573 .views()
@@ -2901,19 +2581,17 @@ fn jumplist_picker(cx: &mut Context) {
29012581 |cx, meta, action| {
29022582 cx.editor.switch(meta.id, action);
29032583 let config = cx.editor.config();
2904− let (view, doc) = (view_mut!(cx.editor), doc_mut!(cx.editor, &meta.id));
2584+ let (view, doc) = current!(cx.editor);
29052585 doc.set_selection(view.id, meta.selection.clone());
2906− if action.align_view(view, doc.id()) {
2907− view.ensure_cursor_in_view_center(doc, config.scrolloff);
2908− }
2586+ view.ensure_cursor_in_view_center(doc, config.scrolloff);
29092587 },
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)));
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))))
2592+ },
2593+ );
2594+ cx.push_layer(Box::new(overlayed(picker)));
29172595}
29182596
29192597impl ui::menu::Item for MappableCommand {
@@ -2946,9 +2624,6 @@ impl ui::menu::Item for MappableCommand {
29462624}
29472625
29482626pub fn command_palette(cx: &mut Context) {
2949− let register = cx.register;
2950− let count = cx.count;
2951−
29522627 cx.callback = Some(Box::new(
29532628 move |compositor: &mut Compositor, cx: &mut compositor::Context| {
29542629 let keymap = compositor.find::<ui::EditorView>().unwrap().keymaps.map()
@@ -2966,8 +2641,8 @@ pub fn command_palette(cx: &mut Context) {
29662641
29672642 let picker = Picker::new(commands, keymap, move |cx, command, _action| {
29682643 let mut ctx = Context {
2969− register,
2970− count,
2644+ register: None,
2645+ count: std::num::NonZeroUsize::new(1),
29712646 editor: cx.editor,
29722647 callback: None,
29732648 on_next_key_callback: None,
@@ -2990,7 +2665,7 @@ pub fn command_palette(cx: &mut Context) {
29902665 }
29912666 }
29922667 });
2993− compositor.push(Box::new(overlaid(picker)));
2668+ compositor.push(Box::new(overlayed(picker)));
29942669 },
29952670 ));
29962671}
@@ -3006,89 +2681,25 @@ fn last_picker(cx: &mut Context) {
30062681 }));
30072682}
30082683
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.
2684+// I inserts at the first nonwhitespace character of each line with a selection
30172685fn insert_at_line_start(cx: &mut Context) {
3018− insert_with_indent(cx, IndentFallbackPos::LineStart);
2686+ goto_first_nonwhitespace(cx);
2687+ enter_insert_mode(cx);
30192688}
30202689
3021−// `A` inserts at the end of each line with a selection.
3022−// If the line is empty, automatically indent.
2690+// A inserts at the end of each line with a selection
30232691fn 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) {
30302692 enter_insert_mode(cx);
3031−
30322693 let (view, doc) = current!(cx.editor);
30332694
3034− let text = doc.text().slice(..);
3035− let contents = doc.text();
3036− let selection = doc.selection(view.id);
3037−
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−}
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+}
30922703
30932704// Creates an LspCallback that waits for formatting changes to be computed. When they're done,
30942705// it applies them, but only if the doc hasn't changed.
@@ -3158,7 +2769,6 @@ fn open(cx: &mut Context, open: Open) {
31582769 Open::Below => graphemes::prev_grapheme_boundary(text, range.to()),
31592770 Open::Above => range.from(),
31602771 });
3161−
31622772 let new_line = match open {
31632773 // adjust position to the end of the line (next line - 1)
31642774 Open::Below => cursor_line + 1,
@@ -3166,15 +2776,13 @@ fn open(cx: &mut Context, open: Open) {
31662776 Open::Above => cursor_line,
31672777 };
31682778
3169− let line_num = new_line.saturating_sub(1);
3170−
31712779 // Index to insert newlines after, as well as the char width
31722780 // to use to compensate for those inserted newlines.
31732781 let (line_end_index, line_end_offset_width) = if new_line == 0 {
31742782 (0, 0)
31752783 } else {
31762784 (
3177− line_end_char_index(&text, line_num),
2785+ line_end_char_index(&doc.text().slice(..), new_line.saturating_sub(1)),
31782786 doc.line_ending.len_chars(),
31792787 )
31802788 };
@@ -3182,15 +2790,13 @@ fn open(cx: &mut Context, open: Open) {
31822790 let indent = indent::indent_for_newline(
31832791 doc.language_config(),
31842792 doc.syntax(),
3185− &doc.config.load().indent_heuristic,
31862793 &doc.indent_style,
31872794 doc.tab_width(),
31882795 text,
3189− line_num,
2796+ new_line.saturating_sub(1),
31902797 line_end_index,
31912798 cursor_line,
31922799 );
3193−
31942800 let indent_len = indent.len();
31952801 let mut text = String::with_capacity(1 + indent_len);
31962802 text.push_str(doc.line_ending.as_str());
@@ -3350,7 +2956,7 @@ fn exit_select_mode(cx: &mut Context) {
33502956
33512957fn goto_first_diag(cx: &mut Context) {
33522958 let (view, doc) = current!(cx.editor);
3353− let selection = match doc.shown_diagnostics().next() {
2959+ let selection = match doc.diagnostics().first() {
33542960 Some(diag) => Selection::single(diag.range.start, diag.range.end),
33552961 None => return,
33562962 };
@@ -3359,7 +2965,7 @@ fn goto_first_diag(cx: &mut Context) {
33592965
33602966fn goto_last_diag(cx: &mut Context) {
33612967 let (view, doc) = current!(cx.editor);
3362− let selection = match doc.shown_diagnostics().last() {
2968+ let selection = match doc.diagnostics().last() {
33632969 Some(diag) => Selection::single(diag.range.start, diag.range.end),
33642970 None => return,
33652971 };
@@ -3375,9 +2981,10 @@ fn goto_next_diag(cx: &mut Context) {
33752981 .cursor(doc.text().slice(..));
33762982
33772983 let diag = doc
3378− .shown_diagnostics()
2984+ .diagnostics()
2985+ .iter()
33792986 .find(|diag| diag.range.start > cursor_pos)
3380− .or_else(|| doc.shown_diagnostics().next());
2987+ .or_else(|| doc.diagnostics().first());
33812988
33822989 let selection = match diag {
33832990 Some(diag) => Selection::single(diag.range.start, diag.range.end),
@@ -3395,10 +3002,11 @@ fn goto_prev_diag(cx: &mut Context) {
33953002 .cursor(doc.text().slice(..));
33963003
33973004 let diag = doc
3398− .shown_diagnostics()
3005+ .diagnostics()
3006+ .iter()
33993007 .rev()
34003008 .find(|diag| diag.range.start < cursor_pos)
3401− .or_else(|| doc.shown_diagnostics().last());
3009+ .or_else(|| doc.diagnostics().last());
34023010
34033011 let selection = match diag {
34043012 // NOTE: the selection is reversed because we're jumping to the
@@ -3489,7 +3097,8 @@ fn goto_next_change_impl(cx: &mut Context, direction: Direction) {
34893097
34903098 doc.set_selection(view.id, selection)
34913099 };
3492− cx.editor.apply_motion(motion);
3100+ motion(cx.editor);
3101+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
34933102}
34943103
34953104/// Returns the [Range] for a [Hunk] in the given text.
@@ -3552,19 +3161,23 @@ pub mod insert {
35523161 use helix_lsp::lsp;
35533162 // if ch matches completion char, trigger completion
35543163 let doc = doc_mut!(cx.editor);
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− });
3164+ let language_server = match doc.language_server() {
3165+ Some(language_server) => language_server,
3166+ None => return,
3167+ };
35643168
3565− if trigger_completion {
3566− cx.editor.clear_idle_timer();
3567− super::completion(cx);
3169+ let capabilities = language_server.capabilities();
3170+
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+ }
35683181 }
35693182 }
35703183
@@ -3572,12 +3185,12 @@ pub mod insert {
35723185 use helix_lsp::lsp;
35733186 // if ch matches signature_help char, trigger
35743187 let doc = doc_mut!(cx.editor);
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;
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,
35813194 };
35823195
35833196 let capabilities = language_server.capabilities();
@@ -3616,7 +3229,6 @@ pub mod insert {
36163229 }
36173230
36183231 use helix_core::auto_pairs;
3619− use helix_view::editor::SmartTabConfig;
36203232
36213233 pub fn insert_char(cx: &mut Context, c: char) {
36223234 let (view, doc) = current_ref!(cx.editor);
@@ -3642,31 +3254,6 @@ pub mod insert {
36423254 }
36433255 }
36443256
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−
36703257 pub fn insert_tab(cx: &mut Context) {
36713258 let (view, doc) = current!(cx.editor);
36723259 // TODO: round out to nearest indentation level (for example a line with 3 spaces should
@@ -3722,7 +3309,6 @@ pub mod insert {
37223309 let indent = indent::indent_for_newline(
37233310 doc.language_config(),
37243311 doc.syntax(),
3725− &doc.config.load().indent_heuristic,
37263312 &doc.indent_style,
37273313 doc.tab_width(),
37283314 text,
@@ -3796,10 +3382,10 @@ pub mod insert {
37963382 let auto_pairs = doc.auto_pairs(cx.editor);
37973383
37983384 let transaction =
3799− Transaction::delete_by_selection(doc.text(), doc.selection(view.id), |range| {
3385+ Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
38003386 let pos = range.cursor(text);
38013387 if pos == 0 {
3802− return (pos, pos);
3388+ return (pos, pos, None);
38033389 }
38043390 let line_start_pos = text.line_to_char(range.cursor_line(text));
38053391 // consider to delete by indent level if all characters before `pos` are indent units.
@@ -3807,7 +3393,11 @@ pub mod insert {
38073393 if !fragment.is_empty() && fragment.chars().all(|ch| ch == ' ' || ch == '\t') {
38083394 if text.get_char(pos.saturating_sub(1)) == Some('\t') {
38093395 // fast path, delete one char
3810− (graphemes::nth_prev_grapheme_boundary(text, pos, 1), pos)
3396+ (
3397+ graphemes::nth_prev_grapheme_boundary(text, pos, 1),
3398+ pos,
3399+ None,
3400+ )
38113401 } else {
38123402 let width: usize = fragment
38133403 .chars()
@@ -3834,7 +3424,7 @@ pub mod insert {
38343424 _ => break,
38353425 }
38363426 }
3837− (start, pos) // delete!
3427+ (start, pos, None) // delete!
38383428 }
38393429 } else {
38403430 match (
@@ -3852,12 +3442,17 @@ pub mod insert {
38523442 (
38533443 graphemes::nth_prev_grapheme_boundary(text, pos, count),
38543444 graphemes::nth_next_grapheme_boundary(text, pos, count),
3445+ None,
38553446 )
38563447 }
38573448 _ =>
38583449 // delete 1 char
38593450 {
3860− (graphemes::nth_prev_grapheme_boundary(text, pos, count), pos)
3451+ (
3452+ graphemes::nth_prev_grapheme_boundary(text, pos, count),
3453+ pos,
3454+ None,
3455+ )
38613456 }
38623457 }
38633458 }
@@ -3870,40 +3465,50 @@ pub mod insert {
38703465
38713466 pub fn delete_char_forward(cx: &mut Context) {
38723467 let count = cx.count();
3873− delete_by_selection_insert_mode(
3874− cx,
3875− |text, range| {
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| {
38763472 let pos = range.cursor(text);
3877− (pos, graphemes::nth_next_grapheme_boundary(text, pos, count))
3878− },
3879− Direction::Forward,
3880− )
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);
38813482 }
38823483
38833484 pub fn delete_word_backward(cx: &mut Context) {
38843485 let count = cx.count();
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− );
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);
38953497 }
38963498
38973499 pub fn delete_word_forward(cx: &mut Context) {
38983500 let count = cx.count();
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− );
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);
39073512 }
39083513}
39093514
@@ -3963,21 +3568,34 @@ fn commit_undo_checkpoint(cx: &mut Context) {
39633568// Yank / Paste
39643569
39653570fn yank(cx: &mut Context) {
3966− yank_impl(cx.editor, cx.register.unwrap_or('"'));
3967− exit_select_mode(cx);
3968−}
3571+ let (view, doc) = current!(cx.editor);
3572+ let text = doc.text().slice(..);
39693573
3970−fn yank_to_clipboard(cx: &mut Context) {
3971− yank_impl(cx.editor, '+');
3972− exit_select_mode(cx);
3973−}
3574+ let values: Vec<String> = doc
3575+ .selection(view.id)
3576+ .fragments(text)
3577+ .map(Cow::into_owned)
3578+ .collect();
39743579
3975−fn yank_to_primary_clipboard(cx: &mut Context) {
3976− yank_impl(cx.editor, '*');
3580+ let msg = format!(
3581+ "yanked {} selection(s) to register {}",
3582+ values.len(),
3583+ cx.register.unwrap_or('"')
3584+ );
3585+
3586+ cx.editor
3587+ .registers
3588+ .write(cx.register.unwrap_or('"'), values);
3589+
3590+ cx.editor.set_status(msg);
39773591 exit_select_mode(cx);
39783592}
39793593
3980−fn yank_impl(editor: &mut Editor, register: char) {
3594+fn yank_joined_to_clipboard_impl(
3595+ editor: &mut Editor,
3596+ separator: &str,
3597+ clipboard_type: ClipboardType,
3598+) -> anyhow::Result<()> {
39813599 let (view, doc) = current!(editor);
39823600 let text = doc.text().slice(..);
39833601
@@ -3986,79 +3604,74 @@ fn yank_impl(editor: &mut Editor, register: char) {
39863604 .fragments(text)
39873605 .map(Cow::into_owned)
39883606 .collect();
3989− let selections = values.len();
39903607
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−}
3608+ let clipboard_text = match clipboard_type {
3609+ ClipboardType::Clipboard => "system clipboard",
3610+ ClipboardType::Selection => "primary clipboard",
3611+ };
39993612
4000−fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
4001− let (view, doc) = current!(editor);
4002− let text = doc.text().slice(..);
3613+ let msg = format!(
3614+ "joined and yanked {} selection(s) to {}",
3615+ values.len(),
3616+ clipboard_text,
3617+ );
40033618
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− });
3619+ let joined = values.join(separator);
40153620
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−}
3621+ editor
3622+ .clipboard_provider
3623+ .set_contents(joined, clipboard_type)
3624+ .context("Couldn't set system clipboard content")?;
40243625
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);
4029−}
3626+ editor.set_status(msg);
40303627
4031−fn yank_joined_to_clipboard(cx: &mut Context) {
4032− let line_ending = doc!(cx.editor).line_ending;
4033− yank_joined_impl(cx.editor, line_ending.as_str(), '+');
4034− exit_select_mode(cx);
3628+ Ok(())
40353629}
40363630
4037−fn yank_joined_to_primary_clipboard(cx: &mut Context) {
3631+fn yank_joined_to_clipboard(cx: &mut Context) {
40383632 let line_ending = doc!(cx.editor).line_ending;
4039− yank_joined_impl(cx.editor, line_ending.as_str(), '*');
3633+ let _ =
3634+ yank_joined_to_clipboard_impl(cx.editor, line_ending.as_str(), ClipboardType::Clipboard);
40403635 exit_select_mode(cx);
40413636}
40423637
4043−fn yank_primary_selection_impl(editor: &mut Editor, register: char) {
3638+fn yank_main_selection_to_clipboard_impl(
3639+ editor: &mut Editor,
3640+ clipboard_type: ClipboardType,
3641+) -> anyhow::Result<()> {
40443642 let (view, doc) = current!(editor);
40453643 let text = doc.text().slice(..);
40463644
4047− let selection = doc.selection(view.id).primary().fragment(text).to_string();
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+ };
3649+
3650+ let value = doc.selection(view.id).primary().fragment(text);
40483651
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()),
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);
40523657 }
3658+
3659+ editor.set_status(message_text);
3660+ Ok(())
40533661}
40543662
40553663fn yank_main_selection_to_clipboard(cx: &mut Context) {
4056− yank_primary_selection_impl(cx.editor, '+');
4057− exit_select_mode(cx);
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);
40583671}
40593672
40603673fn yank_main_selection_to_primary_clipboard(cx: &mut Context) {
4061− yank_primary_selection_impl(cx.editor, '*');
3674+ let _ = yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection);
40623675 exit_select_mode(cx);
40633676}
40643677
@@ -4156,99 +3769,146 @@ pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) {
41563769 };
41573770 let (view, doc) = current!(cx.editor);
41583771 paste_impl(&[contents], doc, view, paste, count, cx.editor.mode);
4159− exit_select_mode(cx);
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+ }
41603788}
41613789
41623790fn paste_clipboard_after(cx: &mut Context) {
4163− paste(cx.editor, '+', Paste::After, cx.count());
4164− exit_select_mode(cx);
3791+ let _ = paste_clipboard_impl(
3792+ cx.editor,
3793+ Paste::After,
3794+ ClipboardType::Clipboard,
3795+ cx.count(),
3796+ );
41653797}
41663798
41673799fn paste_clipboard_before(cx: &mut Context) {
4168− paste(cx.editor, '+', Paste::Before, cx.count());
4169− exit_select_mode(cx);
3800+ let _ = paste_clipboard_impl(
3801+ cx.editor,
3802+ Paste::Before,
3803+ ClipboardType::Clipboard,
3804+ cx.count(),
3805+ );
41703806}
41713807
41723808fn paste_primary_clipboard_after(cx: &mut Context) {
4173− paste(cx.editor, '*', Paste::After, cx.count());
4174− exit_select_mode(cx);
3809+ let _ = paste_clipboard_impl(
3810+ cx.editor,
3811+ Paste::After,
3812+ ClipboardType::Selection,
3813+ cx.count(),
3814+ );
41753815}
41763816
41773817fn paste_primary_clipboard_before(cx: &mut Context) {
4178− paste(cx.editor, '*', Paste::Before, cx.count());
4179− exit_select_mode(cx);
3818+ let _ = paste_clipboard_impl(
3819+ cx.editor,
3820+ Paste::Before,
3821+ ClipboardType::Selection,
3822+ cx.count(),
3823+ );
41803824}
41813825
41823826fn replace_with_yanked(cx: &mut Context) {
4183− replace_with_yanked_impl(cx.editor, cx.register.unwrap_or('"'), cx.count());
4184− exit_select_mode(cx);
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+ }
41853857}
41863858
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();
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);
41923865
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)
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);
42103879 }
4211− });
3880+ Err(e) => return Err(e.context("Couldn't get system clipboard contents")),
3881+ }
42123882
4213− doc.apply(&transaction, view.id);
3883+ exit_select_mode(cx);
3884+ Ok(())
42143885}
42153886
42163887fn replace_selections_with_clipboard(cx: &mut Context) {
4217− replace_with_yanked_impl(cx.editor, '+', cx.count());
4218− exit_select_mode(cx);
3888+ let _ = replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard);
42193889}
42203890
42213891fn replace_selections_with_primary_clipboard(cx: &mut Context) {
4222− replace_with_yanked_impl(cx.editor, '*', cx.count());
4223− exit_select_mode(cx);
3892+ let _ = replace_selections_with_clipboard_impl(cx, ClipboardType::Selection);
42243893}
42253894
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();
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;
42293900
4230− let (view, doc) = current!(editor);
4231− paste_impl(&values, doc, view, pos, count, editor.mode);
3901+ if let Some(values) = registers.read(reg_name) {
3902+ paste_impl(values, doc, view, pos, count, cx.editor.mode);
3903+ }
42323904}
42333905
42343906fn paste_after(cx: &mut Context) {
4235− paste(
4236− cx.editor,
4237− cx.register.unwrap_or('"'),
4238− Paste::After,
4239− cx.count(),
4240− );
4241− exit_select_mode(cx);
3907+ paste(cx, Paste::After)
42423908}
42433909
42443910fn paste_before(cx: &mut Context) {
4245− paste(
4246− cx.editor,
4247− cx.register.unwrap_or('"'),
4248− Paste::Before,
4249− cx.count(),
4250− );
4251− exit_select_mode(cx);
3911+ paste(cx, Paste::Before)
42523912}
42533913
42543914fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
@@ -4287,7 +3947,6 @@ fn indent(cx: &mut Context) {
42873947 }),
42883948 );
42893949 doc.apply(&transaction, view.id);
4290− exit_select_mode(cx);
42913950}
42923951
42933952fn unindent(cx: &mut Context) {
@@ -4327,76 +3986,71 @@ fn unindent(cx: &mut Context) {
43273986 let transaction = Transaction::change(doc.text(), changes.into_iter());
43283987
43293988 doc.apply(&transaction, view.id);
4330− exit_select_mode(cx);
43313989}
43323990
43333991fn format_selections(cx: &mut Context) {
43343992 use helix_lsp::{lsp, util::range_to_lsp_range};
43353993
43363994 let (view, doc) = current!(cx.editor);
4337− let view_id = view.id;
43383995
43393996 // via lsp if available
43403997 // TODO: else via tree-sitter indentation calculations
43413998
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;
3999+ let language_server = match doc.language_server() {
4000+ Some(language_server) => language_server,
4001+ None => return,
43624002 };
43634003
4364− let offset_encoding = language_server.offset_encoding();
43654004 let ranges: Vec<lsp::Range> = doc
4366− .selection(view_id)
4005+ .selection(view.id)
43674006 .iter()
4368− .map(|range| range_to_lsp_range(doc.text(), *range, offset_encoding))
4007+ .map(|range| range_to_lsp_range(doc.text(), *range, language_server.offset_encoding()))
43694008 .collect();
43704009
4010+ if ranges.len() != 1 {
4011+ cx.editor
4012+ .set_error("format_selections only supports a single selection for now");
4013+ return;
4014+ }
4015+
43714016 // TODO: handle fails
43724017 // TODO: concurrent map over all ranges
43734018
43744019 let range = ranges[0];
43754020
4376− let future = language_server
4377− .text_document_range_formatting(
4378− doc.identifier(),
4379− range,
4380− lsp::FormattingOptions::default(),
4381− None,
4382− )
4383− .unwrap();
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+ };
43844034
4385− let edits = tokio::task::block_in_place(|| helix_lsp::block_on(future)).unwrap_or_default();
4035+ let edits = tokio::task::block_in_place(|| helix_lsp::block_on(request)).unwrap_or_default();
43864036
4387− let transaction =
4388− helix_lsp::util::generate_transaction_from_edits(doc.text(), edits, offset_encoding);
4037+ let transaction = helix_lsp::util::generate_transaction_from_edits(
4038+ doc.text(),
4039+ edits,
4040+ language_server.offset_encoding(),
4041+ );
43894042
4390− doc.apply(&transaction, view_id);
4043+ doc.apply(&transaction, view.id);
43914044}
43924045
43934046fn join_selections_impl(cx: &mut Context, select_space: bool) {
43944047 use movement::skip_while;
43954048 let (view, doc) = current!(cx.editor);
43964049 let text = doc.text();
4397− let slice = text.slice(..);
4050+ let slice = doc.text().slice(..);
43984051
43994052 let mut changes = Vec::new();
4053+ let fragment = Tendril::from(" ");
44004054
44014055 for selection in doc.selection(view.id) {
44024056 let (start, mut end) = selection.line_range(slice);
@@ -4412,13 +4066,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
44124066 let mut end = text.line_to_char(line + 1);
44134067 end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
44144068
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));
4069+ // need to skip from start, not end
4070+ let change = (start, end, Some(fragment.clone()));
4071+ changes.push(change);
44224072 }
44234073 }
44244074
@@ -4430,6 +4080,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
44304080 changes.sort_unstable_by_key(|(from, _to, _text)| *from);
44314081 changes.dedup();
44324082
4083+ // TODO: joining multiple empty lines should be replaced by a single space.
4084+ // need to merge change ranges that touch
4085+
44334086 // select inserted spaces
44344087 let transaction = if select_space {
44354088 let ranges: SmallVec<_> = changes
@@ -4441,9 +4094,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
44414094 })
44424095 .collect();
44434096 let selection = Selection::new(ranges, 0);
4444− Transaction::change(text, changes.into_iter()).with_selection(selection)
4097+ Transaction::change(doc.text(), changes.into_iter()).with_selection(selection)
44454098 } else {
4446− Transaction::change(text, changes.into_iter())
4099+ Transaction::change(doc.text(), changes.into_iter())
44474100 };
44484101
44494102 doc.apply(&transaction, view.id);
@@ -4457,8 +4110,8 @@ fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) {
44574110 if remove { "remove:" } else { "keep:" }.into(),
44584111 Some(reg),
44594112 ui::completers::none,
4460− move |cx, regex, event| {
4461− let (view, doc) = current!(cx.editor);
4113+ move |editor, regex, event| {
4114+ let (view, doc) = current!(editor);
44624115 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
44634116 return;
44644117 }
@@ -4517,75 +4170,35 @@ pub fn completion(cx: &mut Context) {
45174170
45184171 let (view, doc) = current!(cx.editor);
45194172
4520− let savepoint = if let Some(CompleteAction::Selected { savepoint }) = &cx.editor.last_completion
4521− {
4522− savepoint.clone()
4523− } else {
4524− doc.savepoint(view)
4173+ let language_server = match doc.language_server() {
4174+ Some(language_server) => language_server,
4175+ None => return,
45254176 };
45264177
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();
4178+ let offset_encoding = language_server.offset_encoding();
4179+ let text = doc.text().slice(..);
4180+ let cursor = doc.selection(view.id).primary().cursor(text);
45624181
4563− anyhow::Ok(items)
4564− }
4565− })
4566− .collect();
4182+ let pos = pos_to_lsp_pos(doc.text(), cursor, offset_encoding);
4183+
4184+ let future = match language_server.completion(doc.identifier(), pos, None) {
4185+ Some(future) => future,
4186+ None => return,
4187+ };
45674188
4568− // setup a channel that allows the request to be canceled
4189+ // setup a chanel that allows the request to be canceled
45694190 let (tx, rx) = oneshot::channel();
45704191 // set completion_request so that this request can be canceled
45714192 // by setting completion_request, the old channel stored there is dropped
45724193 // and the associated request is automatically dropped
45734194 cx.editor.completion_request_handle = Some(tx);
45744195 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− };
45834196 tokio::select! {
45844197 biased;
45854198 _ = rx => {
4586− Ok(Vec::new())
4199+ Ok(serde_json::Value::Null)
45874200 }
4588− res = items_future => {
4201+ res = future => {
45894202 res
45904203 }
45914204 }
@@ -4601,6 +4214,7 @@ pub fn completion(cx: &mut Context) {
46014214 iter.reverse();
46024215 let offset = iter.take_while(|ch| chars::char_is_word(*ch)).count();
46034216 let start_offset = cursor.saturating_sub(offset);
4217+ let savepoint = doc.savepoint(view);
46044218
46054219 let trigger_doc = doc.id();
46064220 let trigger_view = view.id;
@@ -4619,45 +4233,46 @@ pub fn completion(cx: &mut Context) {
46194233 },
46204234 ));
46214235
4622− cx.jobs.callback(async move {
4623− let items = future.await?;
4624− let call = move |editor: &mut Editor, compositor: &mut Compositor| {
4236+ cx.callback(
4237+ future,
4238+ move |editor, compositor, response: Option<lsp::CompletionResponse>| {
46254239 let (view, doc) = current_ref!(editor);
46264240 // check if the completion request is stale.
46274241 //
4628− // Completions are completed asynchronously and therefore the user could
4242+ // Completions are completed asynchrounsly and therefore the user could
46294243 //switch document/view or leave insert mode. In all of thoise cases the
46304244 // completion should be discarded
46314245 if editor.mode != Mode::Insert || view.id != trigger_view || doc.id() != trigger_doc {
46324246 return;
46334247 }
46344248
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+
46354259 if items.is_empty() {
46364260 // editor.set_error("No completion available");
46374261 return;
46384262 }
46394263 let size = compositor.size();
46404264 let ui = compositor.find::<ui::EditorView>().unwrap();
4641− let completion_area = ui.set_completion(
4265+ ui.set_completion(
46424266 editor,
46434267 savepoint,
46444268 items,
4269+ offset_encoding,
46454270 start_offset,
46464271 trigger_offset,
46474272 size,
46484273 );
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− });
4274+ },
4275+ );
46614276}
46624277
46634278// comments
@@ -4692,13 +4307,7 @@ fn rotate_selections_backward(cx: &mut Context) {
46924307 rotate_selections(cx, Direction::Backward)
46934308}
46944309
4695−enum ReorderStrategy {
4696− RotateForward,
4697− RotateBackward,
4698− Reverse,
4699−}
4700−
4701−fn reorder_selection_contents(cx: &mut Context, strategy: ReorderStrategy) {
4310+fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
47024311 let count = cx.count;
47034312 let (view, doc) = current!(cx.editor);
47044313 let text = doc.text().slice(..);
@@ -4716,10 +4325,9 @@ fn reorder_selection_contents(cx: &mut Context, strategy: ReorderStrategy) {
47164325
47174326 for chunk in fragments.chunks_mut(group) {
47184327 // TODO: also modify main index
4719− match strategy {
4720− ReorderStrategy::RotateForward => chunk.rotate_right(1),
4721− ReorderStrategy::RotateBackward => chunk.rotate_left(1),
4722− ReorderStrategy::Reverse => chunk.reverse(),
4328+ match direction {
4329+ Direction::Forward => chunk.rotate_right(1),
4330+ Direction::Backward => chunk.rotate_left(1),
47234331 };
47244332 }
47254333
@@ -4736,13 +4344,10 @@ fn reorder_selection_contents(cx: &mut Context, strategy: ReorderStrategy) {
47364344}
47374345
47384346fn rotate_selection_contents_forward(cx: &mut Context) {
4739− reorder_selection_contents(cx, ReorderStrategy::RotateForward)
4347+ rotate_selection_contents(cx, Direction::Forward)
47404348}
47414349fn rotate_selection_contents_backward(cx: &mut Context) {
4742− reorder_selection_contents(cx, ReorderStrategy::RotateBackward)
4743−}
4744−fn reverse_selection_contents(cx: &mut Context) {
4745− reorder_selection_contents(cx, ReorderStrategy::Reverse)
4350+ rotate_selection_contents(cx, Direction::Backward)
47464351}
47474352
47484353// tree sitter node selection
@@ -4766,7 +4371,8 @@ fn expand_selection(cx: &mut Context) {
47664371 }
47674372 }
47684373 };
4769− cx.editor.apply_motion(motion);
4374+ motion(cx.editor);
4375+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
47704376}
47714377
47724378fn shrink_selection(cx: &mut Context) {
@@ -4790,7 +4396,8 @@ fn shrink_selection(cx: &mut Context) {
47904396 doc.set_selection(view.id, selection);
47914397 }
47924398 };
4793− cx.editor.apply_motion(motion);
4399+ motion(cx.editor);
4400+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
47944401}
47954402
47964403fn select_sibling_impl<F>(cx: &mut Context, sibling_fn: &'static F)
@@ -4808,7 +4415,8 @@ where
48084415 doc.set_selection(view.id, selection);
48094416 }
48104417 };
4811− cx.editor.apply_motion(motion);
4418+ motion(cx.editor);
4419+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
48124420}
48134421
48144422fn select_next_sibling(cx: &mut Context) {
@@ -4819,68 +4427,22 @@ fn select_prev_sibling(cx: &mut Context) {
48194427 select_sibling_impl(cx, &|node| Node::prev_sibling(&node))
48204428}
48214429
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−
48654430fn match_brackets(cx: &mut Context) {
48664431 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(..);
4870−
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− });
48824432
4883− doc.set_selection(view.id, selection);
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+ }
48844446}
48854447
48864448//
@@ -4975,19 +4537,17 @@ fn transpose_view(cx: &mut Context) {
49754537 cx.editor.transpose_view()
49764538}
49774539
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);
4540+// split helper, clear it later
4541+fn split(cx: &mut Context, action: Action) {
4542+ let (view, doc) = current!(cx.editor);
49834543 let id = doc.id();
49844544 let selection = doc.selection(view.id).clone();
49854545 let offset = view.offset;
49864546
4987− editor.switch(id, action);
4547+ cx.editor.switch(id, action);
49884548
49894549 // match the selection in the previous view
4990− let (view, doc) = current!(editor);
4550+ let (view, doc) = current!(cx.editor);
49914551 doc.set_selection(view.id, selection);
49924552 // match the view scroll offset (switch doesn't handle this fully
49934553 // since the selection is only matched after the split)
@@ -4995,7 +4555,7 @@ fn split(editor: &mut Editor, action: Action) {
49954555}
49964556
49974557fn hsplit(cx: &mut Context) {
4998− split(cx.editor, Action::HorizontalSplit);
4558+ split(cx, Action::HorizontalSplit);
49994559}
50004560
50014561fn hsplit_new(cx: &mut Context) {
@@ -5003,7 +4563,7 @@ fn hsplit_new(cx: &mut Context) {
50034563}
50044564
50054565fn vsplit(cx: &mut Context) {
5006− split(cx.editor, Action::VerticalSplit);
4566+ split(cx, Action::VerticalSplit);
50074567}
50084568
50094569fn vsplit_new(cx: &mut Context) {
@@ -5052,12 +4612,7 @@ fn insert_register(cx: &mut Context) {
50524612 if let Some(ch) = event.char() {
50534613 cx.editor.autoinfo = None;
50544614 cx.register = Some(ch);
5055− paste(
5056− cx.editor,
5057− cx.register.unwrap_or('"'),
5058− Paste::Cursor,
5059− cx.count(),
5060− );
4615+ paste(cx, Paste::Cursor);
50614616 }
50624617 })
50634618}
@@ -5141,7 +4696,8 @@ fn goto_ts_object_impl(cx: &mut Context, object: &'static str, direction: Direct
51414696 editor.set_status("Syntax-tree is not available in current buffer");
51424697 }
51434698 };
5144− cx.editor.apply_motion(motion);
4699+ motion(cx.editor);
4700+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
51454701}
51464702
51474703fn goto_next_function(cx: &mut Context) {
@@ -5262,7 +4818,8 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
52624818 });
52634819 doc.set_selection(view.id, selection);
52644820 };
5265− cx.editor.apply_motion(textobject);
4821+ textobject(cx.editor);
4822+ cx.editor.last_motion = Some(Motion(Box::new(textobject)));
52664823 }
52674824 });
52684825
@@ -5352,18 +4909,11 @@ fn surround_replace(cx: &mut Context) {
53524909 }
53534910 };
53544911
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−
53624912 cx.on_next_key(move |cx, event| {
53634913 let (view, doc) = current!(cx.editor);
53644914 let to = match event.char() {
53654915 Some(to) => to,
5366− None => return doc.set_selection(view.id, selection),
4916+ None => return,
53674917 };
53684918 let (open, close) = surround::get_pair(to);
53694919 let transaction = Transaction::change(
@@ -5374,7 +4924,6 @@ fn surround_replace(cx: &mut Context) {
53744924 (pos, pos + 1, Some(t))
53754925 }),
53764926 );
5377− doc.set_selection(view.id, selection);
53784927 doc.apply(&transaction, view.id);
53794928 exit_select_mode(cx);
53804929 });
@@ -5520,10 +5069,9 @@ async fn shell_impl_async(
55205069 let output = if let Some(mut stdin) = process.stdin.take() {
55215070 let input_task = tokio::spawn(async move {
55225071 if let Some(input) = input {
5523− helix_view::document::to_writer(&mut stdin, (encoding::UTF_8, false), &input)
5524− .await?;
5072+ helix_view::document::to_writer(&mut stdin, encoding::UTF_8, &input).await?;
55255073 }
5526− anyhow::Ok(())
5074+ Ok::<_, anyhow::Error>(())
55275075 });
55285076 let (output, _) = tokio::join! {
55295077 process.wait_with_output(),
@@ -5609,18 +5157,12 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
56095157 };
56105158
56115159 // These `usize`s cannot underflow because selection ranges cannot overlap.
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");
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;
56175163 let new_range = Range::new(anchor, anchor + output_len).with_direction(range.direction());
56185164 ranges.push(new_range);
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");
5165+ offset = offset + output_len as isize - deleted_len as isize;
56245166
56255167 changes.push((from, to, Some(output)));
56265168 }
@@ -5642,7 +5184,7 @@ fn shell_prompt(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBeha
56425184 cx,
56435185 prompt,
56445186 Some('|'),
5645− ui::completers::filename,
5187+ ui::completers::none,
56465188 move |cx, input: &str, event: PromptEvent| {
56475189 if event != PromptEvent::Validate {
56485190 return;
@@ -5759,7 +5301,6 @@ fn increment_impl(cx: &mut Context, increment_direction: IncrementDirection) {
57595301 let transaction = Transaction::change(doc.text(), changes.into_iter());
57605302 let transaction = transaction.with_selection(new_selection);
57615303 doc.apply(&transaction, view.id);
5762− exit_select_mode(cx);
57635304 }
57645305}
57655306
@@ -5778,12 +5319,9 @@ fn record_macro(cx: &mut Context) {
57785319 }
57795320 })
57805321 .collect::<String>();
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− }
5322+ cx.editor.registers.write(reg, vec![s]);
5323+ cx.editor
5324+ .set_status(format!("Recorded to register [{}]", reg));
57875325 } else {
57885326 let reg = cx.register.take().unwrap_or('@');
57895327 cx.editor.macro_recording = Some((reg, Vec::new()));
@@ -5803,14 +5341,8 @@ fn replay_macro(cx: &mut Context) {
58035341 return;
58045342 }
58055343
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) {
5344+ let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) {
5345+ match helix_view::input::parse_macro(keys_str) {
58145346 Ok(keys) => keys,
58155347 Err(err) => {
58165348 cx.editor.set_error(format!("Invalid macro: {}", err));