@@ -29,9 +29,10 @@ use helix_core::{
29 tree_sitter::Node,
29 tree_sitter::Node,
30 unicode::width::UnicodeWidthChar,
30 unicode::width::UnicodeWidthChar,
31 visual_offset_from_block, Deletion, LineEnding, Position, Range, Rope, RopeGraphemes,
31 visual_offset_from_block, Deletion, LineEnding, Position, Range, Rope, RopeGraphemes,
32− RopeReader, RopeSlice, Selection, SmallVec, Tendril, Transaction,
32+ RopeSlice, Selection, SmallVec, Tendril, Transaction,
34use helix_view::{
34use helix_view::{
35+ clipboard::ClipboardType,
35 document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
36 document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
36 editor::{Action, CompleteAction},
37 editor::{Action, CompleteAction},
37 info::Info,
38 info::Info,
@@ -43,6 +44,7 @@ use helix_view::{
45use anyhow::{anyhow, bail, ensure, Context as _};
46use anyhow::{anyhow, bail, ensure, Context as _};
47+use fuzzy_matcher::FuzzyMatcher;
46use insert::*;
48use insert::*;
47use movement::Movement;
49use movement::Movement;
@@ -59,14 +61,9 @@ use crate::{
61use crate::job::{self, Jobs};
63use crate::job::{self, Jobs};
62−use futures_util::{stream::FuturesUnordered, TryStreamExt};
64+use futures_util::{stream::FuturesUnordered, StreamExt, TryStreamExt};
64− collections::{HashMap, HashSet},
65+use std::{collections::HashMap, fmt, future::Future};
68− num::NonZeroUsize,
66+use std::{collections::HashSet, num::NonZeroUsize};
72 borrow::Cow,
69 borrow::Cow,
@@ -75,11 +72,11 @@ use std::{
76use once_cell::sync::Lazy;
73use once_cell::sync::Lazy;
77use serde::de::{self, Deserialize, Deserializer};
74use serde::de::{self, Deserialize, Deserializer};
80use grep_regex::RegexMatcherBuilder;
76use grep_regex::RegexMatcherBuilder;
81use grep_searcher::{sinks, BinaryDetection, SearcherBuilder};
77use grep_searcher::{sinks, BinaryDetection, SearcherBuilder};
82use ignore::{DirEntry, WalkBuilder, WalkState};
78use ignore::{DirEntry, WalkBuilder, WalkState};
79+use tokio_stream::wrappers::UnboundedReceiverStream;
84pub type OnKeyCallback = Box<dyn FnOnce(&mut Context, KeyEvent)>;
81pub type OnKeyCallback = Box<dyn FnOnce(&mut Context, KeyEvent)>;
@@ -251,8 +248,6 @@ impl MappableCommand {
251 move_prev_long_word_start, "Move to start of previous long word",
248 move_prev_long_word_start, "Move to start of previous long word",
252 move_next_long_word_end, "Move to end of next long word",
249 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",
250 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",
256 extend_next_word_start, "Extend to start of next word",
251 extend_next_word_start, "Extend to start of next word",
257 extend_prev_word_start, "Extend to start of previous word",
252 extend_prev_word_start, "Extend to start of previous word",
258 extend_next_word_end, "Extend to end of next word",
253 extend_next_word_end, "Extend to end of next word",
@@ -261,8 +256,6 @@ impl MappableCommand {
261 extend_prev_long_word_start, "Extend to start of previous long word",
256 extend_prev_long_word_start, "Extend to start of previous long word",
262 extend_next_long_word_end, "Extend to end of next long word",
257 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",
258 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",
266 find_till_char, "Move till next occurrence of char",
259 find_till_char, "Move till next occurrence of char",
267 find_next_char, "Move to next occurrence of char",
260 find_next_char, "Move to next occurrence of char",
268 extend_till_char, "Extend till next occurrence of char",
261 extend_till_char, "Extend till next occurrence of char",
@@ -337,7 +330,7 @@ impl MappableCommand {
337 goto_implementation, "Goto implementation",
330 goto_implementation, "Goto implementation",
338 goto_file_start, "Goto line number <n> else file start",
331 goto_file_start, "Goto line number <n> else file start",
339 goto_file_end, "Goto file end",
332 goto_file_end, "Goto file end",
340− goto_file, "Goto files/URLs in selection",
333+ goto_file, "Goto files in selection",
341 goto_file_hsplit, "Goto files in selection (hsplit)",
334 goto_file_hsplit, "Goto files in selection (hsplit)",
342 goto_file_vsplit, "Goto files in selection (vsplit)",
335 goto_file_vsplit, "Goto files in selection (vsplit)",
343 goto_reference, "Goto references",
336 goto_reference, "Goto references",
@@ -369,7 +362,6 @@ impl MappableCommand {
369 extend_to_line_end, "Extend to line end",
362 extend_to_line_end, "Extend to line end",
370 extend_to_line_end_newline, "Extend to line end",
363 extend_to_line_end_newline, "Extend to line end",
371 signature_help, "Show signature help",
364 signature_help, "Show signature help",
372− smart_tab, "Insert tab if all cursors have all whitespace to their left; otherwise, run a separate command.",
373 insert_tab, "Insert tab char",
365 insert_tab, "Insert tab char",
374 insert_newline, "Insert newline char",
366 insert_newline, "Insert newline char",
375 delete_char_backward, "Delete previous char",
367 delete_char_backward, "Delete previous char",
@@ -384,8 +376,6 @@ impl MappableCommand {
384 later, "Move forward in history",
376 later, "Move forward in history",
385 commit_undo_checkpoint, "Commit changes to new checkpoint",
377 commit_undo_checkpoint, "Commit changes to new checkpoint",
386 yank, "Yank selection",
378 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",
379 yank_joined, "Join and yank selections",
390 yank_joined_to_clipboard, "Join and yank selections to clipboard",
380 yank_joined_to_clipboard, "Join and yank selections to clipboard",
391 yank_main_selection_to_clipboard, "Yank main selection to clipboard",
381 yank_main_selection_to_clipboard, "Yank main selection to clipboard",
@@ -990,7 +980,6 @@ fn align_selections(cx: &mut Context) {
991 let transaction = Transaction::change(doc.text(), changes.into_iter());
981 let transaction = Transaction::change(doc.text(), changes.into_iter());
992 doc.apply(&transaction, view.id);
982 doc.apply(&transaction, view.id);
993− exit_select_mode(cx);
996fn goto_window(cx: &mut Context, align: Align) {
985fn goto_window(cx: &mut Context, align: Align) {
@@ -1165,10 +1154,6 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
1165 let (view, doc) = current_ref!(cx.editor);
1154 let (view, doc) = current_ref!(cx.editor);
1166 let text = doc.text();
1155 let text = doc.text();
1167 let selections = doc.selection(view.id);
1156 let selections = doc.selection(view.id);
1170− .map(|path| path.parent().unwrap().to_path_buf())
1171− .unwrap_or_default();
1172 let mut paths: Vec<_> = selections
1157 let mut paths: Vec<_> = selections
1174 .map(|r| text.slice(r.from()..r.to()).to_string())
1159 .map(|r| text.slice(r.from()..r.to()).to_string())
@@ -1196,80 +1181,16 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
1196 .to_string(),
1181 .to_string(),
1200 for sel in paths {
1184 for sel in paths {
1201 let p = sel.trim();
1185 let p = sel.trim();
1202− if p.is_empty() {
1186+ if !p.is_empty() {
1206− if let Ok(url) = Url::parse(p) {
1207− return open_url(cx, url, action);
1210− let path = &rel_path.join(p);
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) {
1187+ if let Err(e) = cx.editor.open(&PathBuf::from(p), action) {
1215− cx.editor.set_error(format!("Open file failed: {:?}", e));
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);
1226− .map(|path| path.parent().unwrap().to_path_buf())
1227− .unwrap_or_default();
1229− if url.scheme() != "file" {
1230− return open_external_url(cx, url);
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]))
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());
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) {
1250 cx.editor.set_error(format!("Open file failed: {:?}", e));
1188 cx.editor.set_error(format!("Open file failed: {:?}", e));
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(|_| {})));
1267− Ok(job::Callback::Editor(Box::new(move |editor| {
1268− editor.set_error("Opening URL in external program failed")
1273fn extend_word_impl<F>(cx: &mut Context, extend_fn: F)
1194fn extend_word_impl<F>(cx: &mut Context, extend_fn: F)
1275 F: Fn(RopeSlice, Range, usize) -> Range,
1196 F: Fn(RopeSlice, Range, usize) -> Range,
@@ -1318,65 +1239,6 @@ fn extend_next_long_word_end(cx: &mut Context) {
1318 extend_word_impl(cx, movement::move_next_long_word_end)
1239 extend_word_impl(cx, movement::move_next_long_word_end)
1321−/// Separate branch to find_char designed only for `<ret>` char.
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(
1329− direction: Direction,
1333− let (view, doc) = current!(cx.editor);
1334− let text = doc.text().slice(..);
1336− let selection = doc.selection(view.id).clone().transform(|range| {
1337− let cursor = range.cursor(text);
1338− let cursor_line = range.cursor_line(text);
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 {
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);
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),
1372− range.put_cursor(text, pos, true)
1374− Range::point(range.cursor(text)).put_cursor(text, pos, true)
1377− doc.set_selection(view.id, selection);
1380fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bool) {
1242fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bool) {
1381 // TODO: count is reset to 1 before next key so we move it into the closure here.
1243 // TODO: count is reset to 1 before next key so we move it into the closure here.
1382 // Would be nice to carry over.
1244 // Would be nice to carry over.
@@ -1390,9 +1252,13 @@ fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bo
1390 KeyEvent {
1252 KeyEvent {
1391 code: KeyCode::Enter,
1253 code: KeyCode::Enter,
1394− find_char_line_ending(cx, count, direction, inclusive, extend);
1256+ // TODO: this isn't quite correct when CRLF is involved.
1395− return;
1257+ // This hack will work in most cases, since documents don't
1258+ // usually mix line endings. But we should fix it eventually
1261+ doc!(cx.editor).line_ending.as_str().chars().next().unwrap()
1398 KeyEvent {
1264 KeyEvent {
@@ -1594,7 +1460,6 @@ where
1596 doc.apply(&transaction, view.id);
1462 doc.apply(&transaction, view.id);
1597− exit_select_mode(cx);
1600fn switch_case(cx: &mut Context) {
1465fn switch_case(cx: &mut Context) {
@@ -1836,8 +1701,8 @@ fn select_regex(cx: &mut Context) {
1836 "select:".into(),
1701 "select:".into(),
1837 Some(reg),
1702 Some(reg),
1838 ui::completers::none,
1703 ui::completers::none,
1839− move |cx, regex, event| {
1704+ move |editor, regex, event| {
1840− let (view, doc) = current!(cx.editor);
1705+ let (view, doc) = current!(editor);
1841 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
1706 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
@@ -1858,8 +1723,8 @@ fn split_selection(cx: &mut Context) {
1858 "split:".into(),
1723 "split:".into(),
1859 Some(reg),
1724 Some(reg),
1860 ui::completers::none,
1725 ui::completers::none,
1861− move |cx, regex, event| {
1726+ move |editor, regex, event| {
1862− let (view, doc) = current!(cx.editor);
1727+ let (view, doc) = current!(editor);
1863 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
1728 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
@@ -1982,11 +1847,11 @@ fn search_impl(
1983fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
1848fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
1984 let mut items = reg
1849 let mut items = reg
1985− .and_then(|reg| cx.editor.registers.read(reg, cx.editor))
1850+ .and_then(|reg| cx.editor.registers.get(reg))
1986− .map_or(Vec::new(), |reg| reg.take(200).collect());
1851+ .map_or(Vec::new(), |reg| reg.read().iter().take(200).collect());
1987 items.sort_unstable();
1852 items.sort_unstable();
1988 items.dedup();
1853 items.dedup();
1989− items.into_iter().map(|value| value.to_string()).collect()
1854+ items.into_iter().cloned().collect()
1992fn search(cx: &mut Context) {
1857fn search(cx: &mut Context) {
@@ -2023,14 +1888,12 @@ fn searcher(cx: &mut Context, direction: Direction) {
2023 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
1888 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
2024 .collect()
1889 .collect()
2026− move |cx, regex, event| {
1891+ move |editor, regex, event| {
2027− if event == PromptEvent::Validate {
1892+ if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
2028− cx.editor.registers.last_search_register = reg;
2029− } else if event != PromptEvent::Update {
2032 search_impl(
1895 search_impl(
2033− cx.editor,
1896+ editor,
2034 &contents,
1897 &contents,
2036 Movement::Move,
1899 Movement::Move,
@@ -2045,13 +1908,11 @@ fn searcher(cx: &mut Context, direction: Direction) {
2046fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) {
1909fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) {
2047 let count = cx.count();
1910 let count = cx.count();
2050− .unwrap_or(cx.editor.registers.last_search_register);
2051 let config = cx.editor.config();
1911 let config = cx.editor.config();
2052 let scrolloff = config.scrolloff;
1912 let scrolloff = config.scrolloff;
2053− if let Some(query) = cx.editor.registers.first(register, cx.editor) {
1913+ let (_, doc) = current!(cx.editor);
2054− let doc = doc!(cx.editor);
1914+ let registers = &cx.editor.registers;
1915+ if let Some(query) = registers.read('/').and_then(|query| query.last()) {
2055 let contents = doc.text().slice(..).to_string();
1916 let contents = doc.text().slice(..).to_string();
2056 let search_config = &config.search;
1917 let search_config = &config.search;
2057 let case_insensitive = if search_config.smart_case {
1918 let case_insensitive = if search_config.smart_case {
@@ -2060,7 +1921,7 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
2062 let wrap_around = search_config.wrap_around;
1923 let wrap_around = search_config.wrap_around;
2063− if let Ok(regex) = RegexBuilder::new(&query)
1924+ if let Ok(regex) = RegexBuilder::new(query)
2064 .case_insensitive(case_insensitive)
1925 .case_insensitive(case_insensitive)
2065 .multi_line(true)
1926 .multi_line(true)
2066 .build()
1927 .build()
@@ -2100,7 +1961,6 @@ fn extend_search_prev(cx: &mut Context) {
2102fn search_selection(cx: &mut Context) {
1963fn search_selection(cx: &mut Context) {
2103− let register = cx.register.unwrap_or('/');
2104 let (view, doc) = current!(cx.editor);
1964 let (view, doc) = current!(cx.editor);
2105 let contents = doc.text().slice(..);
1965 let contents = doc.text().slice(..);
@@ -2113,24 +1973,13 @@ fn search_selection(cx: &mut Context) {
2113 .collect::<Vec<_>>()
1973 .collect::<Vec<_>>()
2114 .join("|");
1974 .join("|");
2116− let msg = format!("register '{}' set to '{}'", register, ®ex);
1976+ let msg = format!("register '{}' set to '{}'", '/', ®ex);
2117− match cx.editor.registers.push(register, regex) {
1977+ cx.editor.registers.push('/', regex);
2119− cx.editor.registers.last_search_register = register;
2120− cx.editor.set_status(msg)
1978+ cx.editor.set_status(msg);
2122− Err(err) => cx.editor.set_error(err.to_string()),
2126fn make_search_word_bounded(cx: &mut Context) {
1981fn 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
2132− .unwrap_or(cx.editor.registers.last_search_register);
2133− let regex = match cx.editor.registers.first(register, cx.editor) {
1982+ let regex = match cx.editor.registers.last('/') {
2134 Some(regex) => regex,
1983 Some(regex) => regex,
2135 None => return,
1984 None => return,
@@ -2148,19 +1997,14 @@ fn make_search_word_bounded(cx: &mut Context) {
2148 if !start_anchored {
1997 if !start_anchored {
2149 new_regex.push_str("\\b");
1998 new_regex.push_str("\\b");
2151− new_regex.push_str(®ex);
2000+ new_regex.push_str(regex);
2152 if !end_anchored {
2001 if !end_anchored {
2153 new_regex.push_str("\\b");
2002 new_regex.push_str("\\b");
2156− let msg = format!("register '{}' set to '{}'", register, &new_regex);
2005+ let msg = format!("register '{}' set to '{}'", '/', &new_regex);
2157− match cx.editor.registers.push(register, new_regex) {
2006+ cx.editor.registers.push('/', new_regex);
2159− cx.editor.registers.last_search_register = register;
2160− cx.editor.set_status(msg)
2007+ cx.editor.set_status(msg);
2162− Err(err) => cx.editor.set_error(err.to_string()),
2166fn global_search(cx: &mut Context) {
2010fn global_search(cx: &mut Context) {
@@ -2199,11 +2043,13 @@ fn global_search(cx: &mut Context) {
2046+ let (all_matches_sx, all_matches_rx) = tokio::sync::mpsc::unbounded_channel::<FileResult>();
2202 let config = cx.editor.config();
2047 let config = cx.editor.config();
2203 let smart_case = config.search.smart_case;
2048 let smart_case = config.search.smart_case;
2204 let file_picker_config = config.file_picker.clone();
2049 let file_picker_config = config.file_picker.clone();
2206 let reg = cx.register.unwrap_or('/');
2051 let reg = cx.register.unwrap_or('/');
2207 let completions = search_completions(cx, Some(reg));
2053 let completions = search_completions(cx, Some(reg));
2208 ui::regex_prompt(
2054 ui::regex_prompt(
@@ -2216,67 +2062,43 @@ fn global_search(cx: &mut Context) {
2216 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
2062 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
2217 .collect()
2063 .collect()
2219− move |cx, regex, event| {
2065+ move |_editor, regex, event| {
2220 if event != PromptEvent::Validate {
2066 if event != PromptEvent::Validate {
2223− cx.editor.registers.last_search_register = reg;
2225− let current_path = doc_mut!(cx.editor).path().cloned();
2226− let documents: Vec<_> = cx
2229− .map(|doc| (doc.path().cloned(), doc.text().to_owned()))
2232 if let Ok(matcher) = RegexMatcherBuilder::new()
2070 if let Ok(matcher) = RegexMatcherBuilder::new()
2233 .case_smart(smart_case)
2071 .case_smart(smart_case)
2234 .build(regex.as_str())
2072 .build(regex.as_str())
2236− let search_root = helix_loader::current_working_dir();
2074+ let searcher = SearcherBuilder::new()
2237− if !search_root.exists() {
2075+ .binary_detection(BinaryDetection::quit(b'\x00'))
2238− cx.editor
2076+ .build();
2239− .set_error("Current working directory does not exist");
2243− let (picker, injector) = Picker::stream(current_path);
2078+ let search_root = std::env::current_dir()
2079+ .expect("Global search error: Failed to get current dir");
2245 let dedup_symlinks = file_picker_config.deduplicate_links;
2080 let dedup_symlinks = file_picker_config.deduplicate_links;
2246 let absolute_root = search_root
2081 let absolute_root = search_root
2247 .canonicalize()
2082 .canonicalize()
2248 .unwrap_or_else(|_| search_root.clone());
2083 .unwrap_or_else(|_| search_root.clone());
2249− let injector_ = injector.clone();
2084+
2251− std::thread::spawn(move || {
2252− let searcher = SearcherBuilder::new()
2253− .binary_detection(BinaryDetection::quit(b'\x00'))
2256− let mut walk_builder = WalkBuilder::new(search_root);
2085+ WalkBuilder::new(search_root)
2259− .hidden(file_picker_config.hidden)
2086+ .hidden(file_picker_config.hidden)
2260− .parents(file_picker_config.parents)
2087+ .parents(file_picker_config.parents)
2261− .ignore(file_picker_config.ignore)
2088+ .ignore(file_picker_config.ignore)
2262− .follow_links(file_picker_config.follow_symlinks)
2089+ .follow_links(file_picker_config.follow_symlinks)
2263− .git_ignore(file_picker_config.git_ignore)
2090+ .git_ignore(file_picker_config.git_ignore)
2264− .git_global(file_picker_config.git_global)
2091+ .git_global(file_picker_config.git_global)
2265− .git_exclude(file_picker_config.git_exclude)
2092+ .git_exclude(file_picker_config.git_exclude)
2266− .max_depth(file_picker_config.max_depth)
2093+ .max_depth(file_picker_config.max_depth)
2267− .filter_entry(move |entry| {
2094+ .filter_entry(move |entry| {
2268− filter_picker_entry(entry, &absolute_root, dedup_symlinks)
2095+ filter_picker_entry(entry, &absolute_root, dedup_symlinks)
2272− .add_custom_ignore_filename(helix_loader::config_dir().join("ignore"));
2273− walk_builder.add_custom_ignore_filename(".helix/ignore");
2275− walk_builder.build_parallel().run(|| {
2097+ .build_parallel()
2276 let mut searcher = searcher.clone();
2099 let mut searcher = searcher.clone();
2277 let matcher = matcher.clone();
2100 let matcher = matcher.clone();
2278− let injector = injector_.clone();
2101+ let all_matches_sx = all_matches_sx.clone();
2279− let documents = &documents;
2280 Box::new(move |entry: Result<DirEntry, ignore::Error>| -> WalkState {
2102 Box::new(move |entry: Result<DirEntry, ignore::Error>| -> WalkState {
2281 let entry = match entry {
2103 let entry = match entry {
2282 Ok(entry) => entry,
2104 Ok(entry) => entry,
@@ -2289,39 +2111,17 @@ fn global_search(cx: &mut Context) {
2289 _ => return WalkState::Continue,
2111 _ => return WalkState::Continue,
2292− let mut stop = false;
2114+ let result = searcher.search_path(
2293− let sink = sinks::UTF8(|line_num, _| {
2117+ sinks::UTF8(|line_num, _| {
2294− stop = injector
2118+ all_matches_sx
2295− .push(FileResult::new(entry.path(), line_num as usize - 1))
2119+ .send(FileResult::new(entry.path(), line_num as usize - 1))
2296− .is_err();
2120+ .unwrap();
2298− Ok(!stop)
2122+ Ok(true)
2300− let doc = documents.iter().find(|&(doc_path, _)| {
2303− .map_or(false, |doc_path| doc_path == entry.path())
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)
2316− searcher.search_reader(
2318− RopeReader::new(doc.slice(..)),
2323− searcher.search_path(&matcher, entry.path(), sink)
2326 if let Err(err) = result {
2126 if let Err(err) = result {
2327 log::error!(
2127 log::error!(
@@ -2330,66 +2130,65 @@ fn global_search(cx: &mut Context) {
2336− WalkState::Continue
2133+ WalkState::Continue
2342− cx.jobs.callback(async move {
2343− let call = move |_: &mut Editor, compositor: &mut Compositor| {
2344− let picker = Picker::with_stream(
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),
2351− cx.editor.set_error(format!(
2352− "Failed to open file '{}': {}",
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.",
2369− let start = text.line_to_char(line_num);
2370− let end = text.line_to_char((line_num + 1).min(text.len_lines()));
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);
2379− |_editor, FileResult { path, line_num }| {
2380− Some((path.clone().into(), Some((*line_num, *line_num))))
2383− compositor.push(Box::new(overlaid(picker)))
2385− Ok(Callback::EditorCompositor(Box::new(call)))
2387 } else {
2136 } else {
2388 // Otherwise do nothing
2137 // Otherwise do nothing
2389 // log::warn!("Global Search Invalid Pattern")
2138 // log::warn!("Global Search Invalid Pattern")
2143+ let current_path = doc_mut!(cx.editor).path().cloned();
2145+ let show_picker = async move {
2146+ let all_matches: Vec<FileResult> =
2147+ UnboundedReceiverStream::new(all_matches_rx).collect().await;
2148+ let call: job::Callback = Callback::EditorCompositor(Box::new(
2149+ move |editor: &mut Editor, compositor: &mut Compositor| {
2150+ if all_matches.is_empty() {
2151+ editor.set_status("No matches found");
2155+ let picker = Picker::new(
2158+ move |cx, FileResult { path, line_num }, action| {
2159+ match cx.editor.open(path, action) {
2162+ cx.editor.set_error(format!(
2163+ "Failed to open file '{}': {}",
2171+ let line_num = *line_num;
2172+ let (view, doc) = current!(cx.editor);
2173+ let text = doc.text();
2174+ if line_num >= text.len_lines() {
2175+ cx.editor.set_error("The line you jumped to does not exist anymore because the file has changed.");
2178+ let start = text.line_to_char(line_num);
2179+ let end = text.line_to_char((line_num + 1).min(text.len_lines()));
2181+ doc.set_selection(view.id, Selection::single(start, end));
2182+ align_view(doc, view, Align::Center);
2183+ }).with_preview(|_editor, FileResult { path, line_num }| {
2184+ Some((path.clone().into(), Some((*line_num, *line_num))))
2186+ compositor.push(Box::new(overlaid(picker)));
2191+ cx.jobs.callback(show_picker);
2395enum Extend {
2194enum Extend {
@@ -2512,35 +2311,17 @@ enum Operation {
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 {
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()
2529fn delete_selection_impl(cx: &mut Context, op: Operation) {
2314fn delete_selection_impl(cx: &mut Context, op: Operation) {
2530 let (view, doc) = current!(cx.editor);
2315 let (view, doc) = current!(cx.editor);
2532 let selection = doc.selection(view.id);
2317 let selection = doc.selection(view.id);
2533− let only_whole_lines = selection_is_linewise(selection, doc.text());
2535 if cx.register != Some('_') {
2319 if cx.register != Some('_') {
2536 // first yank the selection
2320 // first yank the selection
2537 let text = doc.text().slice(..);
2321 let text = doc.text().slice(..);
2538 let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
2322 let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
2539 let reg_name = cx.register.unwrap_or('"');
2323 let reg_name = cx.register.unwrap_or('"');
2540− if let Err(err) = cx.editor.registers.write(reg_name, values) {
2324+ cx.editor.registers.write(reg_name, values);
2541− cx.editor.set_error(err.to_string());
2546 // then delete
2327 // then delete
@@ -2554,11 +2335,7 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) {
2554 exit_select_mode(cx);
2335 exit_select_mode(cx);
2556 Operation::Change => {
2337 Operation::Change => {
2557− if only_whole_lines {
2560− enter_insert_mode(cx);
2338+ enter_insert_mode(cx);
@@ -2680,10 +2457,6 @@ fn insert_mode(cx: &mut Context) {
2680 .transform(|range| Range::new(range.to(), range.from()));
2457 .transform(|range| Range::new(range.to(), range.from()));
2682 doc.set_selection(view.id, selection);
2459 doc.set_selection(view.id, selection);
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();
2689// inserts at the end of each selection
2462// inserts at the end of each selection
@@ -2720,10 +2493,6 @@ fn append_mode(cx: &mut Context) {
2721fn file_picker(cx: &mut Context) {
2494fn file_picker(cx: &mut Context) {
2722 let root = find_workspace().0;
2495 let root = find_workspace().0;
2723− if !root.exists() {
2724− cx.editor.set_error("Workspace directory does not exist");
2727 let picker = ui::file_picker(root, &cx.editor.config());
2496 let picker = ui::file_picker(root, &cx.editor.config());
2728 cx.push_layer(Box::new(overlaid(picker)));
2497 cx.push_layer(Box::new(overlaid(picker)));
@@ -2744,14 +2513,8 @@ fn file_picker_in_current_buffer_directory(cx: &mut Context) {
2744 let picker = ui::file_picker(path, &cx.editor.config());
2513 let picker = ui::file_picker(path, &cx.editor.config());
2745 cx.push_layer(Box::new(overlaid(picker)));
2514 cx.push_layer(Box::new(overlaid(picker)));
2748fn file_picker_in_current_directory(cx: &mut Context) {
2516fn file_picker_in_current_directory(cx: &mut Context) {
2749− let cwd = helix_loader::current_working_dir();
2517+ let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("./"));
2752− .set_error("Current working directory does not exist");
2755 let picker = ui::file_picker(cwd, &cx.editor.config());
2518 let picker = ui::file_picker(cwd, &cx.editor.config());
2756 cx.push_layer(Box::new(overlaid(picker)));
2519 cx.push_layer(Box::new(overlaid(picker)));
@@ -2901,11 +2664,9 @@ fn jumplist_picker(cx: &mut Context) {
2901 |cx, meta, action| {
2664 |cx, meta, action| {
2902 cx.editor.switch(meta.id, action);
2665 cx.editor.switch(meta.id, action);
2903 let config = cx.editor.config();
2666 let config = cx.editor.config();
2904− let (view, doc) = (view_mut!(cx.editor), doc_mut!(cx.editor, &meta.id));
2667+ let (view, doc) = current!(cx.editor);
2905 doc.set_selection(view.id, meta.selection.clone());
2668 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);
2669+ view.ensure_cursor_in_view_center(doc, config.scrolloff);
2911 .with_preview(|editor, meta| {
2672 .with_preview(|editor, meta| {
@@ -3053,7 +2814,6 @@ fn insert_with_indent(cx: &mut Context, cursor_fallback: IndentFallbackPos) {
3053 let indent = indent::indent_for_newline(
2814 let indent = indent::indent_for_newline(
3054 language_config,
2815 language_config,
3056− &doc.config.load().indent_heuristic,
3057 &doc.indent_style,
2817 &doc.indent_style,
3058 tab_width,
2818 tab_width,
@@ -3158,7 +2918,6 @@ fn open(cx: &mut Context, open: Open) {
3158 Open::Below => graphemes::prev_grapheme_boundary(text, range.to()),
2918 Open::Below => graphemes::prev_grapheme_boundary(text, range.to()),
3159 Open::Above => range.from(),
2919 Open::Above => range.from(),
3162 let new_line = match open {
2921 let new_line = match open {
3163 // adjust position to the end of the line (next line - 1)
2922 // adjust position to the end of the line (next line - 1)
3164 Open::Below => cursor_line + 1,
2923 Open::Below => cursor_line + 1,
@@ -3166,15 +2925,13 @@ fn open(cx: &mut Context, open: Open) {
3166 Open::Above => cursor_line,
2925 Open::Above => cursor_line,
3169− let line_num = new_line.saturating_sub(1);
3171 // Index to insert newlines after, as well as the char width
2928 // Index to insert newlines after, as well as the char width
3172 // to use to compensate for those inserted newlines.
2929 // to use to compensate for those inserted newlines.
3173 let (line_end_index, line_end_offset_width) = if new_line == 0 {
2930 let (line_end_index, line_end_offset_width) = if new_line == 0 {
3175 } else {
2932 } else {
3177− line_end_char_index(&text, line_num),
2934+ line_end_char_index(&doc.text().slice(..), new_line.saturating_sub(1)),
3178 doc.line_ending.len_chars(),
2935 doc.line_ending.len_chars(),
@@ -3182,15 +2939,13 @@ fn open(cx: &mut Context, open: Open) {
3182 let indent = indent::indent_for_newline(
2939 let indent = indent::indent_for_newline(
3183 doc.language_config(),
2940 doc.language_config(),
3184 doc.syntax(),
2941 doc.syntax(),
3185− &doc.config.load().indent_heuristic,
3186 &doc.indent_style,
2942 &doc.indent_style,
3187 doc.tab_width(),
2943 doc.tab_width(),
3189− line_num,
2945+ new_line.saturating_sub(1),
3190 line_end_index,
2946 line_end_index,
3191 cursor_line,
2947 cursor_line,
3194 let indent_len = indent.len();
2949 let indent_len = indent.len();
3195 let mut text = String::with_capacity(1 + indent_len);
2950 let mut text = String::with_capacity(1 + indent_len);
3196 text.push_str(doc.line_ending.as_str());
2951 text.push_str(doc.line_ending.as_str());
@@ -3616,7 +3371,6 @@ pub mod insert {
3618 use helix_core::auto_pairs;
3373 use helix_core::auto_pairs;
3619− use helix_view::editor::SmartTabConfig;
3621 pub fn insert_char(cx: &mut Context, c: char) {
3375 pub fn insert_char(cx: &mut Context, c: char) {
3622 let (view, doc) = current_ref!(cx.editor);
3376 let (view, doc) = current_ref!(cx.editor);
@@ -3642,31 +3396,6 @@ pub mod insert {
3645− pub fn smart_tab(cx: &mut Context) {
3646− let (view, doc) = current_ref!(cx.editor);
3647− let view_id = view.id;
3650− cx.editor.config().smart_tab,
3651− Some(SmartTabConfig { enable: true, .. })
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())
3661− if !cursors_after_whitespace {
3662− move_parent_node_end(cx);
3670 pub fn insert_tab(cx: &mut Context) {
3399 pub fn insert_tab(cx: &mut Context) {
3671 let (view, doc) = current!(cx.editor);
3400 let (view, doc) = current!(cx.editor);
3672 // TODO: round out to nearest indentation level (for example a line with 3 spaces should
3401 // TODO: round out to nearest indentation level (for example a line with 3 spaces should
@@ -3722,7 +3451,6 @@ pub mod insert {
3722 let indent = indent::indent_for_newline(
3451 let indent = indent::indent_for_newline(
3723 doc.language_config(),
3452 doc.language_config(),
3724 doc.syntax(),
3453 doc.syntax(),
3725− &doc.config.load().indent_heuristic,
3726 &doc.indent_style,
3454 &doc.indent_style,
3727 doc.tab_width(),
3455 doc.tab_width(),
@@ -3963,22 +3691,7 @@ fn commit_undo_checkpoint(cx: &mut Context) {
3963// Yank / Paste
3691// Yank / Paste
3965fn yank(cx: &mut Context) {
3693fn yank(cx: &mut Context) {
3966− yank_impl(cx.editor, cx.register.unwrap_or('"'));
3967− exit_select_mode(cx);
3970−fn yank_to_clipboard(cx: &mut Context) {
3971− yank_impl(cx.editor, '+');
3972− exit_select_mode(cx);
3975−fn yank_to_primary_clipboard(cx: &mut Context) {
3976− yank_impl(cx.editor, '*');
3977− exit_select_mode(cx);
3980−fn yank_impl(editor: &mut Editor, register: char) {
3981− let (view, doc) = current!(editor);
3694+ let (view, doc) = current!(cx.editor);
3982 let text = doc.text().slice(..);
3695 let text = doc.text().slice(..);
3984 let values: Vec<String> = doc
3697 let values: Vec<String> = doc
@@ -3986,15 +3699,19 @@ fn yank_impl(editor: &mut Editor, register: char) {
3986 .fragments(text)
3699 .fragments(text)
3987 .map(Cow::into_owned)
3700 .map(Cow::into_owned)
3988 .collect();
3701 .collect();
3989− let selections = values.len();
3991− match editor.registers.write(register, values) {
3703+ let msg = format!(
3992− Ok(_) => editor.set_status(format!(
3993− "yanked {selections} selection{} to register {register}",
3704+ "yanked {} selection(s) to register {}",
3994− if selections == 1 { "" } else { "s" }
3705+ values.len(),
3995− )),
3706+ cx.register.unwrap_or('"')
3996− Err(err) => editor.set_error(err.to_string()),
3707+ );
3711+ .write(cx.register.unwrap_or('"'), values);
3713+ cx.editor.set_status(msg);
3714+ exit_select_mode(cx);
4000fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
3717fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
@@ -4002,7 +3719,6 @@ fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
4002 let text = doc.text().slice(..);
3719 let text = doc.text().slice(..);
4004 let selection = doc.selection(view.id);
3721 let selection = doc.selection(view.id);
4005− let selections = selection.len();
4006 let joined = selection
3722 let joined = selection
4007 .fragments(text)
3723 .fragments(text)
4008 .fold(String::new(), |mut acc, fragment| {
3724 .fold(String::new(), |mut acc, fragment| {
@@ -4013,52 +3729,104 @@ fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
3733+ "joined and yanked {} selection(s) to register {}",
4016− match editor.registers.write(register, vec![joined]) {
3738+ editor.registers.write(register, vec![joined]);
4017− Ok(_) => editor.set_status(format!(
3739+ editor.set_status(msg);
4018− "joined and yanked {selections} selection{} to register {register}",
4019− if selections == 1 { "" } else { "s" }
4021− Err(err) => editor.set_error(err.to_string()),
4025fn yank_joined(cx: &mut Context) {
3742fn yank_joined(cx: &mut Context) {
4026− let separator = doc!(cx.editor).line_ending.as_str();
3743+ let line_ending = doc!(cx.editor).line_ending;
4027− yank_joined_impl(cx.editor, separator, cx.register.unwrap_or('"'));
3744+ let register = cx.register.unwrap_or('"');
3745+ yank_joined_impl(cx.editor, line_ending.as_str(), register);
4028 exit_select_mode(cx);
3746 exit_select_mode(cx);
4031−fn yank_joined_to_clipboard(cx: &mut Context) {
3749+fn yank_joined_to_clipboard_impl(
4032− let line_ending = doc!(cx.editor).line_ending;
3750+ editor: &mut Editor,
4033− yank_joined_impl(cx.editor, line_ending.as_str(), '+');
3751+ separator: &str,
4034− exit_select_mode(cx);
3752+ clipboard_type: ClipboardType,
3753+) -> anyhow::Result<()> {
3754+ let (view, doc) = current!(editor);
3755+ let text = doc.text().slice(..);
3757+ let values: Vec<String> = doc
3758+ .selection(view.id)
3760+ .map(Cow::into_owned)
3763+ let clipboard_text = match clipboard_type {
3764+ ClipboardType::Clipboard => "system clipboard",
3765+ ClipboardType::Selection => "primary clipboard",
3769+ "joined and yanked {} selection(s) to {}",
3774+ let joined = values.join(separator);
3777+ .clipboard_provider
3778+ .set_contents(joined, clipboard_type)
3779+ .context("Couldn't set system clipboard content")?;
3781+ editor.set_status(msg);
4037−fn yank_joined_to_primary_clipboard(cx: &mut Context) {
3786+fn yank_joined_to_clipboard(cx: &mut Context) {
4038 let line_ending = doc!(cx.editor).line_ending;
3787 let line_ending = doc!(cx.editor).line_ending;
4039− yank_joined_impl(cx.editor, line_ending.as_str(), '*');
3789+ yank_joined_to_clipboard_impl(cx.editor, line_ending.as_str(), ClipboardType::Clipboard);
4040 exit_select_mode(cx);
3790 exit_select_mode(cx);
3793+fn yank_main_selection_to_clipboard_impl(
4043−fn yank_primary_selection_impl(editor: &mut Editor, register: char) {
3794+ editor: &mut Editor,
3795+ clipboard_type: ClipboardType,
3796+) -> anyhow::Result<()> {
4044 let (view, doc) = current!(editor);
3797 let (view, doc) = current!(editor);
4045 let text = doc.text().slice(..);
3798 let text = doc.text().slice(..);
3800+ let message_text = match clipboard_type {
3801+ ClipboardType::Clipboard => "yanked main selection to system clipboard",
3802+ ClipboardType::Selection => "yanked main selection to primary clipboard",
4047− let selection = doc.selection(view.id).primary().fragment(text).to_string();
3805+ let value = doc.selection(view.id).primary().fragment(text);
4049− match editor.registers.write(register, vec![selection]) {
3807+ if let Err(e) = editor
4050− Ok(_) => editor.set_status(format!("yanked primary selection to register {register}",)),
3808+ .clipboard_provider
4051− Err(err) => editor.set_error(err.to_string()),
3809+ .set_contents(value.into_owned(), clipboard_type)
3811+ bail!("Couldn't set system clipboard content: {}", e);
3814+ editor.set_status(message_text);
4055fn yank_main_selection_to_clipboard(cx: &mut Context) {
3818fn yank_main_selection_to_clipboard(cx: &mut Context) {
4056− yank_primary_selection_impl(cx.editor, '+');
3819+ let _ = yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Clipboard);
4057− exit_select_mode(cx);
3820+}
3822+fn yank_joined_to_primary_clipboard(cx: &mut Context) {
3823+ let line_ending = doc!(cx.editor).line_ending;
3825+ yank_joined_to_clipboard_impl(cx.editor, line_ending.as_str(), ClipboardType::Selection);
4060fn yank_main_selection_to_primary_clipboard(cx: &mut Context) {
3828fn yank_main_selection_to_primary_clipboard(cx: &mut Context) {
4061− yank_primary_selection_impl(cx.editor, '*');
3829+ let _ = yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection);
4062 exit_select_mode(cx);
3830 exit_select_mode(cx);
@@ -4156,99 +3924,146 @@ pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) {
4157 let (view, doc) = current!(cx.editor);
3925 let (view, doc) = current!(cx.editor);
4158 paste_impl(&[contents], doc, view, paste, count, cx.editor.mode);
3926 paste_impl(&[contents], doc, view, paste, count, cx.editor.mode);
4159− exit_select_mode(cx);
3927+}
3929+fn paste_clipboard_impl(
3930+ editor: &mut Editor,
3932+ clipboard_type: ClipboardType,
3934+) -> anyhow::Result<()> {
3935+ let (view, doc) = current!(editor);
3936+ match editor.clipboard_provider.get_contents(clipboard_type) {
3938+ paste_impl(&[contents], doc, view, action, count, editor.mode);
3941+ Err(e) => Err(e.context("Couldn't get system clipboard contents")),
4162fn paste_clipboard_after(cx: &mut Context) {
3945fn paste_clipboard_after(cx: &mut Context) {
3946+ let _ = paste_clipboard_impl(
4163− paste(cx.editor, '+', Paste::After, cx.count());
3948+ Paste::After,
4164− exit_select_mode(cx);
3949+ ClipboardType::Clipboard,
4167fn paste_clipboard_before(cx: &mut Context) {
3954fn paste_clipboard_before(cx: &mut Context) {
3955+ let _ = paste_clipboard_impl(
4168− paste(cx.editor, '+', Paste::Before, cx.count());
3957+ Paste::Before,
4169− exit_select_mode(cx);
3958+ ClipboardType::Clipboard,
4172fn paste_primary_clipboard_after(cx: &mut Context) {
3963fn paste_primary_clipboard_after(cx: &mut Context) {
3964+ let _ = paste_clipboard_impl(
4173− paste(cx.editor, '*', Paste::After, cx.count());
3966+ Paste::After,
4174− exit_select_mode(cx);
3967+ ClipboardType::Selection,
4177fn paste_primary_clipboard_before(cx: &mut Context) {
3972fn paste_primary_clipboard_before(cx: &mut Context) {
3973+ let _ = paste_clipboard_impl(
4178− paste(cx.editor, '*', Paste::Before, cx.count());
3975+ Paste::Before,
4179− exit_select_mode(cx);
3976+ ClipboardType::Selection,
4182fn replace_with_yanked(cx: &mut Context) {
3981fn replace_with_yanked(cx: &mut Context) {
3982+ let count = cx.count();
4183− replace_with_yanked_impl(cx.editor, cx.register.unwrap_or('"'), cx.count());
3983+ let reg_name = cx.register.unwrap_or('"');
3984+ let (view, doc) = current!(cx.editor);
3985+ let registers = &mut cx.editor.registers;
3987+ if let Some(values) = registers.read(reg_name) {
3988+ if !values.is_empty() {
3989+ let repeat = std::iter::repeat(
3992+ .map(|value| Tendril::from(&value.repeat(count)))
3995+ let mut values = values
3997+ .map(|value| Tendril::from(&value.repeat(count)))
3999+ let selection = doc.selection(view.id);
4000+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
4001+ if !range.is_empty() {
4002+ (range.from(), range.to(), Some(values.next().unwrap()))
4004+ (range.from(), range.to(), None)
4008+ doc.apply(&transaction, view.id);
4184− exit_select_mode(cx);
4009+ exit_select_mode(cx);
4187−fn replace_with_yanked_impl(editor: &mut Editor, register: char, count: usize) {
4014+fn replace_selections_with_clipboard_impl(
4188− let Some(values) = editor.registers
4015+ cx: &mut Context,
4189− .read(register, editor)
4016+ clipboard_type: ClipboardType,
4190− .filter(|values| values.len() > 0) else { return };
4017+) -> anyhow::Result<()> {
4191− let values: Vec<_> = values.map(|value| value.to_string()).collect();
4018+ let count = cx.count();
4019+ let (view, doc) = current!(cx.editor);
4193− let (view, doc) = current!(editor);
4021+ match cx.editor.clipboard_provider.get_contents(clipboard_type) {
4194− let repeat = std::iter::repeat(
4022+ Ok(contents) => {
4197− .map(|value| Tendril::from(&value.repeat(count)))
4200− let mut values = values
4202− .map(|value| Tendril::from(&value.repeat(count)))
4204− let selection = doc.selection(view.id);
4023+ let selection = doc.selection(view.id);
4205− let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
4024+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
4206− if !range.is_empty() {
4025+ (
4207− (range.from(), range.to(), Some(values.next().unwrap()))
4026+ range.from(),
4208− } else {
4027+ range.to(),
4209− (range.from(), range.to(), None)
4028+ Some(contents.repeat(count).as_str().into()),
4032+ doc.apply(&transaction, view.id);
4033+ doc.append_changes_to_history(view);
4035+ Err(e) => return Err(e.context("Couldn't get system clipboard contents")),
4213− doc.apply(&transaction, view.id);
4038+ exit_select_mode(cx);
4216fn replace_selections_with_clipboard(cx: &mut Context) {
4042fn replace_selections_with_clipboard(cx: &mut Context) {
4217− replace_with_yanked_impl(cx.editor, '+', cx.count());
4043+ let _ = replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard);
4218− exit_select_mode(cx);
4221fn replace_selections_with_primary_clipboard(cx: &mut Context) {
4046fn replace_selections_with_primary_clipboard(cx: &mut Context) {
4222− replace_with_yanked_impl(cx.editor, '*', cx.count());
4047+ let _ = replace_selections_with_clipboard_impl(cx, ClipboardType::Selection);
4223− exit_select_mode(cx);
4226−fn paste(editor: &mut Editor, register: char, pos: Paste, count: usize) {
4050+fn paste(cx: &mut Context, pos: Paste) {
4227− let Some(values) = editor.registers.read(register, editor) else { return };
4051+ let count = cx.count();
4228− let values: Vec<_> = values.map(|value| value.to_string()).collect();
4052+ let reg_name = cx.register.unwrap_or('"');
4053+ let (view, doc) = current!(cx.editor);
4054+ let registers = &mut cx.editor.registers;
4230− let (view, doc) = current!(editor);
4056+ if let Some(values) = registers.read(reg_name) {
4231− paste_impl(&values, doc, view, pos, count, editor.mode);
4057+ paste_impl(values, doc, view, pos, count, cx.editor.mode);
4234fn paste_after(cx: &mut Context) {
4061fn paste_after(cx: &mut Context) {
4237− cx.register.unwrap_or('"'),
4238− Paste::After,
4062+ paste(cx, Paste::After)
4241− exit_select_mode(cx);
4244fn paste_before(cx: &mut Context) {
4065fn paste_before(cx: &mut Context) {
4247− cx.register.unwrap_or('"'),
4248− Paste::Before,
4066+ paste(cx, Paste::Before)
4251− exit_select_mode(cx);
4254fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
4069fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
@@ -4287,7 +4102,6 @@ fn indent(cx: &mut Context) {
4289 doc.apply(&transaction, view.id);
4104 doc.apply(&transaction, view.id);
4290− exit_select_mode(cx);
4293fn unindent(cx: &mut Context) {
4107fn unindent(cx: &mut Context) {
@@ -4327,7 +4141,6 @@ fn unindent(cx: &mut Context) {
4327 let transaction = Transaction::change(doc.text(), changes.into_iter());
4141 let transaction = Transaction::change(doc.text(), changes.into_iter());
4329 doc.apply(&transaction, view.id);
4143 doc.apply(&transaction, view.id);
4330− exit_select_mode(cx);
4333fn format_selections(cx: &mut Context) {
4146fn format_selections(cx: &mut Context) {
@@ -4357,7 +4170,7 @@ fn format_selections(cx: &mut Context) {
4359 cx.editor
4172 cx.editor
4360− .set_error("No configured language server supports range formatting");
4173+ .set_error("No configured language server does not support range formatting");
@@ -4394,9 +4207,10 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
4394 use movement::skip_while;
4207 use movement::skip_while;
4395 let (view, doc) = current!(cx.editor);
4208 let (view, doc) = current!(cx.editor);
4396 let text = doc.text();
4209 let text = doc.text();
4397− let slice = text.slice(..);
4210+ let slice = doc.text().slice(..);
4399 let mut changes = Vec::new();
4212 let mut changes = Vec::new();
4213+ let fragment = Tendril::from(" ");
4401 for selection in doc.selection(view.id) {
4215 for selection in doc.selection(view.id) {
4402 let (start, mut end) = selection.line_range(slice);
4216 let (start, mut end) = selection.line_range(slice);
@@ -4412,13 +4226,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
4412 let mut end = text.line_to_char(line + 1);
4226 let mut end = text.line_to_char(line + 1);
4413 end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
4227 end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
4415− let separator = if end == line_end_char_index(&slice, line + 1) {
4229+ // need to skip from start, not end
4416− // the joining line contains only space-characters => don't include a whitespace when joining
4230+ let change = (start, end, Some(fragment.clone()));
4419− Some(Tendril::from(" "))
4421− changes.push((start, end, separator));
4231+ changes.push(change);
@@ -4430,6 +4240,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
4430 changes.sort_unstable_by_key(|(from, _to, _text)| *from);
4240 changes.sort_unstable_by_key(|(from, _to, _text)| *from);
4431 changes.dedup();
4241 changes.dedup();
4243+ // TODO: joining multiple empty lines should be replaced by a single space.
4244+ // need to merge change ranges that touch
4433 // select inserted spaces
4246 // select inserted spaces
4434 let transaction = if select_space {
4247 let transaction = if select_space {
4435 let ranges: SmallVec<_> = changes
4248 let ranges: SmallVec<_> = changes
@@ -4441,9 +4254,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
4442 .collect();
4255 .collect();
4443 let selection = Selection::new(ranges, 0);
4256 let selection = Selection::new(ranges, 0);
4444− Transaction::change(text, changes.into_iter()).with_selection(selection)
4257+ Transaction::change(doc.text(), changes.into_iter()).with_selection(selection)
4445 } else {
4258 } else {
4446− Transaction::change(text, changes.into_iter())
4259+ Transaction::change(doc.text(), changes.into_iter())
4449 doc.apply(&transaction, view.id);
4262 doc.apply(&transaction, view.id);
@@ -4457,8 +4270,8 @@ fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) {
4457 if remove { "remove:" } else { "keep:" }.into(),
4270 if remove { "remove:" } else { "keep:" }.into(),
4458 Some(reg),
4271 Some(reg),
4459 ui::completers::none,
4272 ui::completers::none,
4460− move |cx, regex, event| {
4273+ move |editor, regex, event| {
4461− let (view, doc) = current!(cx.editor);
4274+ let (view, doc) = current!(editor);
4462 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
4275 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
@@ -4819,49 +4632,6 @@ fn select_prev_sibling(cx: &mut Context) {
4819 select_sibling_impl(cx, &|node| Node::prev_sibling(&node))
4632 select_sibling_impl(cx, &|node| Node::prev_sibling(&node))
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);
4826− if let Some(syntax) = doc.syntax() {
4827− let text = doc.text().slice(..);
4828− let current_selection = doc.selection(view.id);
4830− let selection = movement::move_parent_node_end(
4833− current_selection.clone(),
4838− doc.set_selection(view.id, selection);
4840− // [TODO] temporary workaround until we're not using the idle timer to
4841− // trigger auto completions any more
4842− editor.clear_idle_timer();
4846− cx.editor.apply_motion(motion);
4849−pub fn move_parent_node_end(cx: &mut Context) {
4850− move_node_bound_impl(cx, Direction::Forward, Movement::Move)
4853−pub fn move_parent_node_start(cx: &mut Context) {
4854− move_node_bound_impl(cx, Direction::Backward, Movement::Move)
4857−pub fn extend_parent_node_end(cx: &mut Context) {
4858− move_node_bound_impl(cx, Direction::Forward, Movement::Extend)
4861−pub fn extend_parent_node_start(cx: &mut Context) {
4862− move_node_bound_impl(cx, Direction::Backward, Movement::Extend)
4865fn match_brackets(cx: &mut Context) {
4635fn match_brackets(cx: &mut Context) {
4866 let (view, doc) = current!(cx.editor);
4636 let (view, doc) = current!(cx.editor);
4867 let is_select = cx.editor.mode == Mode::Select;
4637 let is_select = cx.editor.mode == Mode::Select;
@@ -4975,19 +4745,17 @@ fn transpose_view(cx: &mut Context) {
4975 cx.editor.transpose_view()
4745 cx.editor.transpose_view()
4978−/// Open a new split in the given direction specified by the action.
4748+// split helper, clear it later
4980−/// Maintain the current view (both the cursor's position and view in document).
4981−fn split(editor: &mut Editor, action: Action) {
4749+fn split(cx: &mut Context, action: Action) {
4982− let (view, doc) = current!(editor);
4750+ let (view, doc) = current!(cx.editor);
4983 let id = doc.id();
4751 let id = doc.id();
4984 let selection = doc.selection(view.id).clone();
4752 let selection = doc.selection(view.id).clone();
4985 let offset = view.offset;
4753 let offset = view.offset;
4987− editor.switch(id, action);
4755+ cx.editor.switch(id, action);
4989 // match the selection in the previous view
4757 // match the selection in the previous view
4990− let (view, doc) = current!(editor);
4758+ let (view, doc) = current!(cx.editor);
4991 doc.set_selection(view.id, selection);
4759 doc.set_selection(view.id, selection);
4992 // match the view scroll offset (switch doesn't handle this fully
4760 // match the view scroll offset (switch doesn't handle this fully
4993 // since the selection is only matched after the split)
4761 // since the selection is only matched after the split)
@@ -4995,7 +4763,7 @@ fn split(editor: &mut Editor, action: Action) {
4997fn hsplit(cx: &mut Context) {
4765fn hsplit(cx: &mut Context) {
4998− split(cx.editor, Action::HorizontalSplit);
4766+ split(cx, Action::HorizontalSplit);
5001fn hsplit_new(cx: &mut Context) {
4769fn hsplit_new(cx: &mut Context) {
@@ -5003,7 +4771,7 @@ fn hsplit_new(cx: &mut Context) {
5005fn vsplit(cx: &mut Context) {
4773fn vsplit(cx: &mut Context) {
5006− split(cx.editor, Action::VerticalSplit);
4774+ split(cx, Action::VerticalSplit);
5009fn vsplit_new(cx: &mut Context) {
4777fn vsplit_new(cx: &mut Context) {
@@ -5052,12 +4820,7 @@ fn insert_register(cx: &mut Context) {
5052 if let Some(ch) = event.char() {
4820 if let Some(ch) = event.char() {
5053 cx.editor.autoinfo = None;
4821 cx.editor.autoinfo = None;
5054 cx.register = Some(ch);
4822 cx.register = Some(ch);
5057− cx.register.unwrap_or('"'),
5058− Paste::Cursor,
4823+ paste(cx, Paste::Cursor);
@@ -5352,18 +5115,11 @@ fn surround_replace(cx: &mut Context) {
5355− let selection = selection.clone();
5356− let ranges: SmallVec<[Range; 1]> = change_pos.iter().map(|&p| Range::point(p)).collect();
5359− Selection::new(ranges, selection.primary_index() * 2),
5362 cx.on_next_key(move |cx, event| {
5118 cx.on_next_key(move |cx, event| {
5363 let (view, doc) = current!(cx.editor);
5119 let (view, doc) = current!(cx.editor);
5364 let to = match event.char() {
5120 let to = match event.char() {
5365 Some(to) => to,
5121 Some(to) => to,
5366− None => return doc.set_selection(view.id, selection),
5122+ None => return,
5368 let (open, close) = surround::get_pair(to);
5124 let (open, close) = surround::get_pair(to);
5369 let transaction = Transaction::change(
5125 let transaction = Transaction::change(
@@ -5374,7 +5130,6 @@ fn surround_replace(cx: &mut Context) {
5374 (pos, pos + 1, Some(t))
5130 (pos, pos + 1, Some(t))
5377− doc.set_selection(view.id, selection);
5378 doc.apply(&transaction, view.id);
5133 doc.apply(&transaction, view.id);
5379 exit_select_mode(cx);
5134 exit_select_mode(cx);
@@ -5609,18 +5364,12 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
5611 // These `usize`s cannot underflow because selection ranges cannot overlap.
5366 // These `usize`s cannot underflow because selection ranges cannot overlap.
5612− let anchor = to
5367+ // Once the MSRV is 1.66.0 (mixed_integer_ops is stabilized), we can use checked
5613− .checked_add_signed(offset)
5368+ // arithmetic to assert this.
5614− .expect("Selection ranges cannot overlap")
5369+ let anchor = (to as isize + offset - deleted_len as isize) as usize;
5615− .checked_sub(deleted_len)
5616− .expect("Selection ranges cannot overlap");
5617 let new_range = Range::new(anchor, anchor + output_len).with_direction(range.direction());
5370 let new_range = Range::new(anchor, anchor + output_len).with_direction(range.direction());
5618 ranges.push(new_range);
5371 ranges.push(new_range);
5619− offset = offset
5372+ offset = offset + output_len as isize - deleted_len as isize;
5620− .checked_add_unsigned(output_len)
5621− .expect("Selection ranges cannot overlap")
5622− .checked_sub_unsigned(deleted_len)
5623− .expect("Selection ranges cannot overlap");
5625 changes.push((from, to, Some(output)));
5374 changes.push((from, to, Some(output)));
@@ -5759,7 +5508,6 @@ fn increment_impl(cx: &mut Context, increment_direction: IncrementDirection) {
5759 let transaction = Transaction::change(doc.text(), changes.into_iter());
5508 let transaction = Transaction::change(doc.text(), changes.into_iter());
5760 let transaction = transaction.with_selection(new_selection);
5509 let transaction = transaction.with_selection(new_selection);
5761 doc.apply(&transaction, view.id);
5510 doc.apply(&transaction, view.id);
5762− exit_select_mode(cx);
@@ -5778,12 +5526,9 @@ fn record_macro(cx: &mut Context) {
5780 .collect::<String>();
5528 .collect::<String>();
5781− match cx.editor.registers.write(reg, vec![s]) {
5529+ cx.editor.registers.write(reg, vec![s]);
5783− .editor
5530+ cx.editor
5784− .set_status(format!("Recorded to register [{}]", reg)),
5531+ .set_status(format!("Recorded to register [{}]", reg));
5785− Err(err) => cx.editor.set_error(err.to_string()),
5787 } else {
5532 } else {
5788 let reg = cx.register.take().unwrap_or('@');
5533 let reg = cx.register.take().unwrap_or('@');
5789 cx.editor.macro_recording = Some((reg, Vec::new()));
5534 cx.editor.macro_recording = Some((reg, Vec::new()));
@@ -5803,14 +5548,8 @@ fn replay_macro(cx: &mut Context) {
5806− let keys: Vec<KeyEvent> = if let Some(keys) = cx
5551+ let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) {
5809− .read(reg, cx.editor)
5810− .filter(|values| values.len() == 1)
5811− .map(|mut values| values.next().unwrap())
5813− match helix_view::input::parse_macro(&keys) {
5552+ match helix_view::input::parse_macro(keys_str) {
5814 Ok(keys) => keys,
5553 Ok(keys) => keys,
5815 Err(err) => {
5554 Err(err) => {
5816 cx.editor.set_error(format!("Invalid macro: {}", err));
5555 cx.editor.set_error(format!("Invalid macro: {}", err));