@@ -6,8 +6,8 @@ use helix_core::{
66 object, pos_at_coords,
77 regex::{self, Regex},
88 register::Register,
9− search, selection, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection,
10− SmallVec, Tendril, Transaction,
9+ search, selection, surround, textobject, LineEnding, Position, Range, Rope, RopeGraphemes,
10+ RopeSlice, Selection, SmallVec, Tendril, Transaction,
1111};
1212
1313use helix_view::{
@@ -20,7 +20,7 @@ use helix_view::{
2020 Document, DocumentId, Editor, ViewId,
2121};
2222
23−use anyhow::anyhow;
23+use anyhow::{anyhow, bail, Context as _};
2424use helix_lsp::{
2525 lsp,
2626 util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
@@ -37,6 +37,7 @@ use crate::{
3737use crate::job::{self, Job, Jobs};
3838use futures_util::{FutureExt, TryFutureExt};
3939use std::collections::HashMap;
40+use std::num::NonZeroUsize;
4041use std::{fmt, future::Future};
4142
4243use std::{
@@ -49,7 +50,7 @@ use serde::de::{self, Deserialize, Deserializer};
4950
5051pub struct Context<'a> {
5152 pub selected_register: helix_view::RegisterSelection,
52− pub count: Option<std::num::NonZeroUsize>,
53+ pub count: Option<NonZeroUsize>,
5354 pub editor: &'a mut Editor,
5455
5556 pub callback: Option<crate::compositor::Callback>,
@@ -75,7 +76,9 @@ impl<'a> Context<'a> {
7576
7677 #[inline]
7778 pub fn on_next_key_mode(&mut self, map: HashMap<KeyEvent, fn(&mut Context)>) {
79+ let count = self.count;
7880 self.on_next_key(move |cx, event| {
81+ cx.count = count;
7982 cx.editor.autoinfo = None;
8083 if let Some(func) = map.get(&event) {
8184 func(cx);
@@ -180,6 +183,9 @@ impl Command {
180183 extend_till_prev_char,
181184 extend_prev_char,
182185 replace,
186+ switch_case,
187+ switch_to_uppercase,
188+ switch_to_lowercase,
183189 page_up,
184190 page_down,
185191 half_page_up,
@@ -778,6 +784,57 @@ fn replace(cx: &mut Context) {
778784 })
779785}
780786
787+fn switch_case(cx: &mut Context) {
788+ let (view, doc) = current!(cx.editor);
789+ let transaction =
790+ Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
791+ let text: Tendril = range
792+ .fragment(doc.text().slice(..))
793+ .chars()
794+ .flat_map(|ch| {
795+ if ch.is_lowercase() {
796+ ch.to_uppercase().collect()
797+ } else if ch.is_uppercase() {
798+ ch.to_lowercase().collect()
799+ } else {
800+ vec![ch]
801+ }
802+ })
803+ .collect();
804+
805+ (range.from(), range.to() + 1, Some(text))
806+ });
807+
808+ doc.apply(&transaction, view.id);
809+ doc.append_changes_to_history(view.id);
810+}
811+
812+fn switch_to_uppercase(cx: &mut Context) {
813+ let (view, doc) = current!(cx.editor);
814+ let transaction =
815+ Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
816+ let text: Tendril = range.fragment(doc.text().slice(..)).to_uppercase().into();
817+
818+ (range.from(), range.to() + 1, Some(text))
819+ });
820+
821+ doc.apply(&transaction, view.id);
822+ doc.append_changes_to_history(view.id);
823+}
824+
825+fn switch_to_lowercase(cx: &mut Context) {
826+ let (view, doc) = current!(cx.editor);
827+ let transaction =
828+ Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
829+ let text: Tendril = range.fragment(doc.text().slice(..)).to_lowercase().into();
830+
831+ (range.from(), range.to() + 1, Some(text))
832+ });
833+
834+ doc.apply(&transaction, view.id);
835+ doc.append_changes_to_history(view.id);
836+}
837+
781838fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
782839 use Direction::*;
783840 let (view, doc) = current!(cx.editor);
@@ -1194,34 +1251,45 @@ mod cmd {
11941251 pub alias: Option<&'static str>,
11951252 pub doc: &'static str,
11961253 // params, flags, helper, completer
1197− pub fun: fn(&mut compositor::Context, &[&str], PromptEvent),
1254+ pub fun: fn(&mut compositor::Context, &[&str], PromptEvent) -> anyhow::Result<()>,
11981255 pub completer: Option<Completer>,
11991256 }
12001257
1201− fn quit(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1258+ fn quit(
1259+ cx: &mut compositor::Context,
1260+ _args: &[&str],
1261+ _event: PromptEvent,
1262+ ) -> anyhow::Result<()> {
12021263 // last view and we have unsaved changes
1203− if cx.editor.tree.views().count() == 1 && buffers_remaining_impl(cx.editor) {
1204− return;
1264+ if cx.editor.tree.views().count() == 1 {
1265+ buffers_remaining_impl(cx.editor)?
12051266 }
1267+
12061268 cx.editor
12071269 .close(view!(cx.editor).id, /* close_buffer */ false);
1270+
1271+ Ok(())
12081272 }
12091273
1210− fn force_quit(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1274+ fn force_quit(
1275+ cx: &mut compositor::Context,
1276+ _args: &[&str],
1277+ _event: PromptEvent,
1278+ ) -> anyhow::Result<()> {
12111279 cx.editor
12121280 .close(view!(cx.editor).id, /* close_buffer */ false);
1281+
1282+ Ok(())
12131283 }
12141284
1215− fn open(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1216− match args.get(0) {
1217− Some(path) => {
1218− // TODO: handle error
1219− let _ = cx.editor.open(path.into(), Action::Replace);
1220− }
1221− None => {
1222− cx.editor.set_error("wrong argument count".to_string());
1223− }
1224− };
1285+ fn open(
1286+ cx: &mut compositor::Context,
1287+ args: &[&str],
1288+ _event: PromptEvent,
1289+ ) -> anyhow::Result<()> {
1290+ let path = args.get(0).context("wrong argument count")?;
1291+ let _ = cx.editor.open(path.into(), Action::Replace)?;
1292+ Ok(())
12251293 }
12261294
12271295 fn write_impl<P: AsRef<Path>>(
@@ -1232,12 +1300,10 @@ mod cmd {
12321300 let (_, doc) = current!(cx.editor);
12331301
12341302 if let Some(path) = path {
1235− if let Err(err) = doc.set_path(path.as_ref()) {
1236− return Err(anyhow!("invalid filepath: {}", err));
1237− };
1303+ doc.set_path(path.as_ref()).context("invalid filepath")?;
12381304 }
12391305 if doc.path().is_none() {
1240− return Err(anyhow!("cannot write a buffer without a filename"));
1306+ bail!("cannot write a buffer without a filename");
12411307 }
12421308 let fmt = doc.auto_format().map(|fmt| {
12431309 let shared = fmt.shared();
@@ -1253,21 +1319,33 @@ mod cmd {
12531319 Ok(tokio::spawn(doc.format_and_save(fmt)))
12541320 }
12551321
1256− fn write(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1257− match write_impl(cx, args.first()) {
1258− Err(e) => cx.editor.set_error(e.to_string()),
1259− Ok(handle) => {
1260− cx.jobs
1261− .add(Job::new(handle.unwrap_or_else(|e| Err(e.into()))).wait_before_exiting());
1262− }
1263− };
1322+ fn write(
1323+ cx: &mut compositor::Context,
1324+ args: &[&str],
1325+ _event: PromptEvent,
1326+ ) -> anyhow::Result<()> {
1327+ let handle = write_impl(cx, args.first())?;
1328+ cx.jobs
1329+ .add(Job::new(handle.unwrap_or_else(|e| Err(e.into()))).wait_before_exiting());
1330+
1331+ Ok(())
12641332 }
12651333
1266− fn new_file(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1334+ fn new_file(
1335+ cx: &mut compositor::Context,
1336+ _args: &[&str],
1337+ _event: PromptEvent,
1338+ ) -> anyhow::Result<()> {
12671339 cx.editor.new_file(Action::Replace);
1340+
1341+ Ok(())
12681342 }
12691343
1270− fn format(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1344+ fn format(
1345+ cx: &mut compositor::Context,
1346+ _args: &[&str],
1347+ _event: PromptEvent,
1348+ ) -> anyhow::Result<()> {
12711349 let (_, doc) = current!(cx.editor);
12721350
12731351 if let Some(format) = doc.format() {
@@ -1275,9 +1353,14 @@ mod cmd {
12751353 make_format_callback(doc.id(), doc.version(), Modified::LeaveModified, format);
12761354 cx.jobs.callback(callback);
12771355 }
1278− }
12791356
1280− fn set_indent_style(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1357+ Ok(())
1358+ }
1359+ fn set_indent_style(
1360+ cx: &mut compositor::Context,
1361+ args: &[&str],
1362+ _event: PromptEvent,
1363+ ) -> anyhow::Result<()> {
12811364 use IndentStyle::*;
12821365
12831366 // If no argument, report current indent style.
@@ -1289,7 +1372,7 @@ mod cmd {
12891372 Spaces(n) if (2..=8).contains(&n) => format!("{} spaces", n),
12901373 _ => "error".into(), // Shouldn't happen.
12911374 });
1292− return;
1375+ return Ok(());
12931376 }
12941377
12951378 // Attempt to parse argument as an indent style.
@@ -1304,18 +1387,19 @@ mod cmd {
13041387 _ => None,
13051388 };
13061389
1307− if let Some(s) = style {
1308− let doc = doc_mut!(cx.editor);
1309− doc.indent_style = s;
1310− } else {
1311− // Invalid argument.
1312− cx.editor
1313− .set_error(format!("invalid indent style '{}'", args[0],));
1314− }
1390+ let style = style.context("invalid indent style")?;
1391+ let doc = doc_mut!(cx.editor);
1392+ doc.indent_style = style;
1393+
1394+ Ok(())
13151395 }
13161396
13171397 /// Sets or reports the current document's line ending setting.
1318− fn set_line_ending(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1398+ fn set_line_ending(
1399+ cx: &mut compositor::Context,
1400+ args: &[&str],
1401+ _event: PromptEvent,
1402+ ) -> anyhow::Result<()> {
13191403 use LineEnding::*;
13201404
13211405 // If no argument, report current line ending setting.
@@ -1331,7 +1415,8 @@ mod cmd {
13311415 // These should never be a document's default line ending.
13321416 VT | LS | PS => "error".into(),
13331417 });
1334− return;
1418+
1419+ return Ok(());
13351420 }
13361421
13371422 // Attempt to parse argument as a line ending.
@@ -1345,72 +1430,65 @@ mod cmd {
13451430 _ => None,
13461431 };
13471432
1348− if let Some(le) = line_ending {
1349− doc_mut!(cx.editor).line_ending = le;
1350− } else {
1351− // Invalid argument.
1352− cx.editor
1353− .set_error(format!("invalid line ending '{}'", args[0],));
1354− }
1433+ let line_ending = line_ending.context("invalid line ending")?;
1434+ doc_mut!(cx.editor).line_ending = line_ending;
1435+ Ok(())
13551436 }
13561437
1357− fn earlier(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1358− let uk = match args.join(" ").parse::<helix_core::history::UndoKind>() {
1359− Ok(uk) => uk,
1360− Err(msg) => {
1361− cx.editor.set_error(msg);
1362− return;
1363− }
1364− };
1438+ fn earlier(
1439+ cx: &mut compositor::Context,
1440+ args: &[&str],
1441+ _event: PromptEvent,
1442+ ) -> anyhow::Result<()> {
1443+ let uk = args
1444+ .join(" ")
1445+ .parse::<helix_core::history::UndoKind>()
1446+ .map_err(|s| anyhow!(s))?;
1447+
13651448 let (view, doc) = current!(cx.editor);
1366− doc.earlier(view.id, uk)
1449+ doc.earlier(view.id, uk);
1450+
1451+ Ok(())
13671452 }
13681453
1369− fn later(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1370− let uk = match args.join(" ").parse::<helix_core::history::UndoKind>() {
1371− Ok(uk) => uk,
1372− Err(msg) => {
1373− cx.editor.set_error(msg);
1374− return;
1375− }
1376− };
1454+ fn later(
1455+ cx: &mut compositor::Context,
1456+ args: &[&str],
1457+ _event: PromptEvent,
1458+ ) -> anyhow::Result<()> {
1459+ let uk = args
1460+ .join(" ")
1461+ .parse::<helix_core::history::UndoKind>()
1462+ .map_err(|s| anyhow!(s))?;
13771463 let (view, doc) = current!(cx.editor);
1378− doc.later(view.id, uk)
1464+ doc.later(view.id, uk);
1465+
1466+ Ok(())
13791467 }
13801468
1381− fn write_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1382− match write_impl(cx, args.first()) {
1383− Ok(handle) => {
1384− if let Err(e) = helix_lsp::block_on(handle) {
1385− cx.editor.set_error(e.to_string());
1386− } else {
1387− quit(cx, &[], event);
1388− }
1389− }
1390− Err(e) => {
1391− cx.editor.set_error(e.to_string());
1392− }
1393− }
1469+ fn write_quit(
1470+ cx: &mut compositor::Context,
1471+ args: &[&str],
1472+ event: PromptEvent,
1473+ ) -> anyhow::Result<()> {
1474+ let handle = write_impl(cx, args.first())?;
1475+ let _ = helix_lsp::block_on(handle)?;
1476+ quit(cx, &[], event)
13941477 }
13951478
1396− fn force_write_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1397− match write_impl(cx, args.first()) {
1398− Ok(handle) => {
1399− if let Err(e) = helix_lsp::block_on(handle) {
1400− cx.editor.set_error(e.to_string());
1401− } else {
1402− force_quit(cx, &[], event);
1403− }
1404− }
1405− Err(e) => {
1406− cx.editor.set_error(e.to_string());
1407− }
1408− }
1479+ fn force_write_quit(
1480+ cx: &mut compositor::Context,
1481+ args: &[&str],
1482+ event: PromptEvent,
1483+ ) -> anyhow::Result<()> {
1484+ let handle = write_impl(cx, args.first())?;
1485+ let _ = helix_lsp::block_on(handle)?;
1486+ force_quit(cx, &[], event)
14091487 }
14101488
1411− /// Returns `true` if there are modified buffers remaining and sets editor error,
1412− /// otherwise returns `false`
1413− fn buffers_remaining_impl(editor: &mut Editor) -> bool {
1489+ /// Results an error if there are modified buffers remaining and sets editor error,
1490+ /// otherwise returns `Ok(())`
1491+ fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()> {
14141492 let modified: Vec<_> = editor
14151493 .documents()
14161494 .filter(|doc| doc.is_modified())
@@ -1421,16 +1499,13 @@ mod cmd {
14211499 })
14221500 .collect();
14231501 if !modified.is_empty() {
1424− let err = format!(
1502+ bail!(
14251503 "{} unsaved buffer(s) remaining: {:?}",
14261504 modified.len(),
14271505 modified
14281506 );
1429− editor.set_error(err);
1430− true
1431− } else {
1432− false
14331507 }
1508+ Ok(())
14341509 }
14351510
14361511 fn write_all_impl(
@@ -1439,7 +1514,7 @@ mod cmd {
14391514 _event: PromptEvent,
14401515 quit: bool,
14411516 force: bool,
1442− ) {
1517+ ) -> anyhow::Result<()> {
14431518 let mut errors = String::new();
14441519
14451520 // save all documents
@@ -1452,11 +1527,10 @@ mod cmd {
14521527 // TODO: handle error.
14531528 let _ = helix_lsp::block_on(tokio::spawn(doc.save()));
14541529 }
1455− editor.set_error(errors);
14561530
14571531 if quit {
1458− if !force && buffers_remaining_impl(editor) {
1459− return;
1532+ if !force {
1533+ buffers_remaining_impl(editor)?;
14601534 }
14611535
14621536 // close all views
@@ -1465,23 +1539,42 @@ mod cmd {
14651539 editor.close(view_id, false);
14661540 }
14671541 }
1542+
1543+ bail!(errors)
14681544 }
14691545
1470− fn write_all(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1546+ fn write_all(
1547+ cx: &mut compositor::Context,
1548+ args: &[&str],
1549+ event: PromptEvent,
1550+ ) -> anyhow::Result<()> {
14711551 write_all_impl(&mut cx.editor, args, event, false, false)
14721552 }
14731553
1474− fn write_all_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1554+ fn write_all_quit(
1555+ cx: &mut compositor::Context,
1556+ args: &[&str],
1557+ event: PromptEvent,
1558+ ) -> anyhow::Result<()> {
14751559 write_all_impl(&mut cx.editor, args, event, true, false)
14761560 }
14771561
1478− fn force_write_all_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1562+ fn force_write_all_quit(
1563+ cx: &mut compositor::Context,
1564+ args: &[&str],
1565+ event: PromptEvent,
1566+ ) -> anyhow::Result<()> {
14791567 write_all_impl(&mut cx.editor, args, event, true, true)
14801568 }
14811569
1482− fn quit_all_impl(editor: &mut Editor, _args: &[&str], _event: PromptEvent, force: bool) {
1483− if !force && buffers_remaining_impl(editor) {
1484− return;
1570+ fn quit_all_impl(
1571+ editor: &mut Editor,
1572+ _args: &[&str],
1573+ _event: PromptEvent,
1574+ force: bool,
1575+ ) -> anyhow::Result<()> {
1576+ if !force {
1577+ buffers_remaining_impl(editor)?;
14851578 }
14861579
14871580 // close all views
@@ -1489,57 +1582,77 @@ mod cmd {
14891582 for view_id in views {
14901583 editor.close(view_id, false);
14911584 }
1585+
1586+ Ok(())
14921587 }
14931588
1494− fn quit_all(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1589+ fn quit_all(
1590+ cx: &mut compositor::Context,
1591+ args: &[&str],
1592+ event: PromptEvent,
1593+ ) -> anyhow::Result<()> {
14951594 quit_all_impl(&mut cx.editor, args, event, false)
14961595 }
14971596
1498− fn force_quit_all(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1597+ fn force_quit_all(
1598+ cx: &mut compositor::Context,
1599+ args: &[&str],
1600+ event: PromptEvent,
1601+ ) -> anyhow::Result<()> {
14991602 quit_all_impl(&mut cx.editor, args, event, true)
15001603 }
15011604
1502− fn theme(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1503− let theme = if let Some(theme) = args.first() {
1504− theme
1505− } else {
1506− cx.editor.set_error("theme name not provided".into());
1507− return;
1508− };
1509−
1510− cx.editor.set_theme_from_name(theme);
1605+ fn theme(
1606+ cx: &mut compositor::Context,
1607+ args: &[&str],
1608+ _event: PromptEvent,
1609+ ) -> anyhow::Result<()> {
1610+ let theme = args.first().context("theme not provided")?;
1611+ cx.editor.set_theme_from_name(theme)
15111612 }
15121613
15131614 fn yank_main_selection_to_clipboard(
15141615 cx: &mut compositor::Context,
15151616 _args: &[&str],
15161617 _event: PromptEvent,
1517− ) {
1518− yank_main_selection_to_clipboard_impl(&mut cx.editor);
1618+ ) -> anyhow::Result<()> {
1619+ yank_main_selection_to_clipboard_impl(&mut cx.editor)
15191620 }
15201621
1521− fn yank_joined_to_clipboard(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1622+ fn yank_joined_to_clipboard(
1623+ cx: &mut compositor::Context,
1624+ args: &[&str],
1625+ _event: PromptEvent,
1626+ ) -> anyhow::Result<()> {
15221627 let (_, doc) = current!(cx.editor);
15231628 let separator = args
15241629 .first()
15251630 .copied()
15261631 .unwrap_or_else(|| doc.line_ending.as_str());
1527− yank_joined_to_clipboard_impl(&mut cx.editor, separator);
1632+ yank_joined_to_clipboard_impl(&mut cx.editor, separator)
15281633 }
15291634
1530− fn paste_clipboard_after(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1531− paste_clipboard_impl(&mut cx.editor, Paste::After);
1635+ fn paste_clipboard_after(
1636+ cx: &mut compositor::Context,
1637+ _args: &[&str],
1638+ _event: PromptEvent,
1639+ ) -> anyhow::Result<()> {
1640+ paste_clipboard_impl(&mut cx.editor, Paste::After)
15321641 }
15331642
1534− fn paste_clipboard_before(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1535− paste_clipboard_impl(&mut cx.editor, Paste::After);
1643+ fn paste_clipboard_before(
1644+ cx: &mut compositor::Context,
1645+ _args: &[&str],
1646+ _event: PromptEvent,
1647+ ) -> anyhow::Result<()> {
1648+ paste_clipboard_impl(&mut cx.editor, Paste::After)
15361649 }
15371650
15381651 fn replace_selections_with_clipboard(
15391652 cx: &mut compositor::Context,
15401653 _args: &[&str],
15411654 _event: PromptEvent,
1542− ) {
1655+ ) -> anyhow::Result<()> {
15431656 let (view, doc) = current!(cx.editor);
15441657
15451658 match cx.editor.clipboard_provider.get_contents() {
@@ -1555,71 +1668,76 @@ mod cmd {
15551668
15561669 doc.apply(&transaction, view.id);
15571670 doc.append_changes_to_history(view.id);
1671+ Ok(())
15581672 }
1559− Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e),
1673+ Err(e) => Err(e.context("Couldn't get system clipboard contents")),
15601674 }
15611675 }
15621676
1563− fn show_clipboard_provider(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1677+ fn show_clipboard_provider(
1678+ cx: &mut compositor::Context,
1679+ _args: &[&str],
1680+ _event: PromptEvent,
1681+ ) -> anyhow::Result<()> {
15641682 cx.editor
15651683 .set_status(cx.editor.clipboard_provider.name().into());
1684+ Ok(())
15661685 }
15671686
1568− fn change_current_directory(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1569− let dir = match args.first() {
1570− Some(dir) => dir,
1571− None => {
1572− cx.editor.set_error("target directory not provided".into());
1573− return;
1574− }
1575− };
1687+ fn change_current_directory(
1688+ cx: &mut compositor::Context,
1689+ args: &[&str],
1690+ _event: PromptEvent,
1691+ ) -> anyhow::Result<()> {
1692+ let dir = args.first().context("target directory not provided")?;
15761693
15771694 if let Err(e) = std::env::set_current_dir(dir) {
1578− cx.editor.set_error(format!(
1579− "Couldn't change the current working directory: {:?}",
1580− e
1581− ));
1582− return;
1695+ bail!("Couldn't change the current working directory: {:?}", e);
15831696 }
15841697
1585− match std::env::current_dir() {
1586− Ok(cwd) => cx.editor.set_status(format!(
1587− "Current working directory is now {}",
1588− cwd.display()
1589− )),
1590− Err(e) => cx
1591− .editor
1592− .set_error(format!("Couldn't get the new working directory: {}", e)),
1593− }
1698+ let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
1699+ cx.editor.set_status(format!(
1700+ "Current working directory is now {}",
1701+ cwd.display()
1702+ ));
1703+ Ok(())
15941704 }
15951705
1596− fn show_current_directory(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1597− match std::env::current_dir() {
1598− Ok(cwd) => cx
1599− .editor
1600− .set_status(format!("Current working directory is {}", cwd.display())),
1601− Err(e) => cx
1602− .editor
1603− .set_error(format!("Couldn't get the current working directory: {}", e)),
1604− }
1706+ fn show_current_directory(
1707+ cx: &mut compositor::Context,
1708+ _args: &[&str],
1709+ _event: PromptEvent,
1710+ ) -> anyhow::Result<()> {
1711+ let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
1712+ cx.editor
1713+ .set_status(format!("Current working directory is {}", cwd.display()));
1714+ Ok(())
16051715 }
16061716
16071717 /// Sets the [`Document`]'s encoding..
1608− fn set_encoding(cx: &mut compositor::Context, args: &[&str], _: PromptEvent) {
1718+ fn set_encoding(
1719+ cx: &mut compositor::Context,
1720+ args: &[&str],
1721+ _event: PromptEvent,
1722+ ) -> anyhow::Result<()> {
16091723 let (_, doc) = current!(cx.editor);
16101724 if let Some(label) = args.first() {
16111725 doc.set_encoding(label)
1612− .unwrap_or_else(|e| cx.editor.set_error(e.to_string()));
16131726 } else {
16141727 let encoding = doc.encoding().name().to_string();
1615− cx.editor.set_status(encoding)
1728+ cx.editor.set_status(encoding);
1729+ Ok(())
16161730 }
16171731 }
16181732
16191733 /// Reload the [`Document`] from its source file.
1620− fn reload(cx: &mut compositor::Context, _args: &[&str], _: PromptEvent) {
1734+ fn reload(
1735+ cx: &mut compositor::Context,
1736+ _args: &[&str],
1737+ _event: PromptEvent,
1738+ ) -> anyhow::Result<()> {
16211739 let (view, doc) = current!(cx.editor);
1622− doc.reload(view.id).unwrap();
1740+ doc.reload(view.id)
16231741 }
16241742
16251743 pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
@@ -1884,7 +2002,9 @@ fn command_mode(cx: &mut Context) {
18842002 }
18852003
18862004 if let Some(cmd) = cmd::COMMANDS.get(parts[0]) {
1887− (cmd.fun)(cx, &parts[1..], event);
2005+ if let Err(e) = (cmd.fun)(cx, &parts[1..], event) {
2006+ cx.editor.set_error(format!("{}", e));
2007+ }
18882008 } else {
18892009 cx.editor
18902010 .set_error(format!("no such command: '{}'", parts[0]));
@@ -2816,7 +2936,7 @@ fn yank(cx: &mut Context) {
28162936 cx.editor.set_status(msg)
28172937}
28182938
2819−fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) {
2939+fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) -> anyhow::Result<()> {
28202940 let (view, doc) = current!(editor);
28212941
28222942 let values: Vec<String> = doc
@@ -2832,19 +2952,22 @@ fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) {
28322952
28332953 let joined = values.join(separator);
28342954
2835− if let Err(e) = editor.clipboard_provider.set_contents(joined) {
2836− log::error!("Couldn't set system clipboard content: {:?}", e);
2837− }
2955+ editor
2956+ .clipboard_provider
2957+ .set_contents(joined)
2958+ .context("Couldn't set system clipboard content")?;
28382959
28392960 editor.set_status(msg);
2961+
2962+ Ok(())
28402963}
28412964
28422965fn yank_joined_to_clipboard(cx: &mut Context) {
28432966 let line_ending = current!(cx.editor).1.line_ending;
2844− yank_joined_to_clipboard_impl(&mut cx.editor, line_ending.as_str());
2967+ let _ = yank_joined_to_clipboard_impl(&mut cx.editor, line_ending.as_str());
28452968}
28462969
2847−fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) {
2970+fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> {
28482971 let (view, doc) = current!(editor);
28492972
28502973 let value = doc
@@ -2853,14 +2976,15 @@ fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) {
28532976 .fragment(doc.text().slice(..));
28542977
28552978 if let Err(e) = editor.clipboard_provider.set_contents(value.into_owned()) {
2856− log::error!("Couldn't set system clipboard content: {:?}", e);
2979+ bail!("Couldn't set system clipboard content: {:?}", e);
28572980 }
28582981
28592982 editor.set_status("yanked main selection to system clipboard".to_owned());
2983+ Ok(())
28602984}
28612985
28622986fn yank_main_selection_to_clipboard(cx: &mut Context) {
2863− yank_main_selection_to_clipboard_impl(&mut cx.editor);
2987+ let _ = yank_main_selection_to_clipboard_impl(&mut cx.editor);
28642988}
28652989
28662990#[derive(Copy, Clone)]
@@ -2909,7 +3033,7 @@ fn paste_impl(
29093033 Some(transaction)
29103034}
29113035
2912−fn paste_clipboard_impl(editor: &mut Editor, action: Paste) {
3036+fn paste_clipboard_impl(editor: &mut Editor, action: Paste) -> anyhow::Result<()> {
29133037 let (view, doc) = current!(editor);
29143038
29153039 match editor
@@ -2920,18 +3044,19 @@ fn paste_clipboard_impl(editor: &mut Editor, action: Paste) {
29203044 Ok(Some(transaction)) => {
29213045 doc.apply(&transaction, view.id);
29223046 doc.append_changes_to_history(view.id);
3047+ Ok(())
29233048 }
2924− Ok(None) => {}
2925− Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e),
3049+ Ok(None) => Ok(()),
3050+ Err(e) => Err(e.context("Couldn't get system clipboard contents")),
29263051 }
29273052}
29283053
29293054fn paste_clipboard_after(cx: &mut Context) {
2930− paste_clipboard_impl(&mut cx.editor, Paste::After);
3055+ let _ = paste_clipboard_impl(&mut cx.editor, Paste::After);
29313056}
29323057
29333058fn paste_clipboard_before(cx: &mut Context) {
2934− paste_clipboard_impl(&mut cx.editor, Paste::Before);
3059+ let _ = paste_clipboard_impl(&mut cx.editor, Paste::Before);
29353060}
29363061
29373062fn replace_with_yanked(cx: &mut Context) {
@@ -2959,7 +3084,7 @@ fn replace_with_yanked(cx: &mut Context) {
29593084 }
29603085}
29613086
2962−fn replace_selections_with_clipboard_impl(editor: &mut Editor) {
3087+fn replace_selections_with_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> {
29633088 let (view, doc) = current!(editor);
29643089
29653090 match editor.clipboard_provider.get_contents() {
@@ -2974,13 +3099,14 @@ fn replace_selections_with_clipboard_impl(editor: &mut Editor) {
29743099
29753100 doc.apply(&transaction, view.id);
29763101 doc.append_changes_to_history(view.id);
3102+ Ok(())
29773103 }
2978− Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e),
3104+ Err(e) => Err(e.context("Couldn't get system clipboard contents")),
29793105 }
29803106}
29813107
29823108fn replace_selections_with_clipboard(cx: &mut Context) {
2983− replace_selections_with_clipboard_impl(&mut cx.editor);
3109+ let _ = replace_selections_with_clipboard_impl(&mut cx.editor);
29843110}
29853111
29863112fn paste_after(cx: &mut Context) {
@@ -3405,24 +3531,6 @@ fn jump_backward(cx: &mut Context) {
34053531 };
34063532}
34073533
3408−fn window_mode(cx: &mut Context) {
3409− cx.on_next_key(move |cx, event| {
3410− if let KeyEvent {
3411− code: KeyCode::Char(ch),
3412− ..
3413− } = event
3414− {
3415− match ch {
3416− 'w' => rotate_view(cx),
3417− 'h' => hsplit(cx),
3418− 'v' => vsplit(cx),
3419− 'q' => wclose(cx),
3420− _ => {}
3421− }
3422− }
3423− })
3424−}
3425−
34263534fn rotate_view(cx: &mut Context) {
34273535 cx.editor.focus_next()
34283536}
@@ -3468,105 +3576,46 @@ fn select_register(cx: &mut Context) {
34683576 })
34693577}
34703578
3471−fn view_mode(cx: &mut Context) {
3472− cx.on_next_key(move |cx, event| {
3473− if let KeyEvent {
3474− code: KeyCode::Char(ch),
3475− ..
3476− } = event
3477− {
3478− // if lock, call cx again
3479− // TODO: temporarily show VIE in the mode list
3480− match ch {
3481− // center
3482− 'z' | 'c'
3483− // top
3484− | 't'
3485− // bottom
3486− | 'b' => {
3487− let (view, doc) = current!(cx.editor);
3579+fn align_view_top(cx: &mut Context) {
3580+ let (view, doc) = current!(cx.editor);
3581+ align_view(doc, view, Align::Top);
3582+}
34883583
3489− align_view(doc, view, match ch {
3490− 'z' | 'c' => Align::Center,
3491− 't' => Align::Top,
3492− 'b' => Align::Bottom,
3493− _ => unreachable!()
3494− });
3495− }
3496− 'm' => {
3497− let (view, doc) = current!(cx.editor);
3498− let pos = doc.selection(view.id).cursor();
3499− let pos = coords_at_pos(doc.text().slice(..), pos);
3584+fn align_view_center(cx: &mut Context) {
3585+ let (view, doc) = current!(cx.editor);
3586+ align_view(doc, view, Align::Center);
3587+}
35003588
3501− const OFFSET: usize = 7; // gutters
3502− view.first_col = pos.col.saturating_sub(((view.area.width as usize).saturating_sub(OFFSET)) / 2);
3503− },
3504− 'h' => (),
3505− 'j' => scroll(cx, 1, Direction::Forward),
3506− 'k' => scroll(cx, 1, Direction::Backward),
3507− 'l' => (),
3508− _ => (),
3509− }
3510− }
3511− })
3589+fn align_view_bottom(cx: &mut Context) {
3590+ let (view, doc) = current!(cx.editor);
3591+ align_view(doc, view, Align::Bottom);
35123592}
35133593
3514−fn left_bracket_mode(cx: &mut Context) {
3515− cx.on_next_key(move |cx, event| {
3516− if let KeyEvent {
3517− code: KeyCode::Char(ch),
3518− ..
3519− } = event
3520− {
3521− match ch {
3522− 'd' => goto_prev_diag(cx),
3523− 'D' => goto_first_diag(cx),
3524− _ => (),
3525− }
3526− }
3527− })
3594+fn align_view_middle(cx: &mut Context) {
3595+ let (view, doc) = current!(cx.editor);
3596+ let pos = doc.selection(view.id).cursor();
3597+ let pos = coords_at_pos(doc.text().slice(..), pos);
3598+
3599+ const OFFSET: usize = 7; // gutters
3600+ view.first_col = pos
3601+ .col
3602+ .saturating_sub(((view.area.width as usize).saturating_sub(OFFSET)) / 2);
35283603}
35293604
3530−fn right_bracket_mode(cx: &mut Context) {
3531− cx.on_next_key(move |cx, event| {
3532− if let KeyEvent {
3533− code: KeyCode::Char(ch),
3534− ..
3535− } = event
3536− {
3537− match ch {
3538− 'd' => goto_next_diag(cx),
3539− 'D' => goto_last_diag(cx),
3540− _ => (),
3541− }
3542− }
3543− })
3605+fn scroll_up(cx: &mut Context) {
3606+ scroll(cx, cx.count(), Direction::Backward);
35443607}
35453608
3546−use helix_core::surround;
3547−use helix_core::textobject;
3609+fn scroll_down(cx: &mut Context) {
3610+ scroll(cx, cx.count(), Direction::Forward);
3611+}
35483612
3549−fn match_mode(cx: &mut Context) {
3550− let count = cx.count;
3551− cx.on_next_key(move |cx, event| {
3552− if let KeyEvent {
3553− code: KeyCode::Char(ch),
3554− ..
3555− } = event
3556− {
3557− // FIXME: count gets reset because of cx.on_next_key()
3558− cx.count = count;
3559− match ch {
3560− 'm' => match_brackets(cx),
3561− 's' => surround_add(cx),
3562− 'r' => surround_replace(cx),
3563− 'd' => surround_delete(cx),
3564− 'a' => select_textobject(cx, textobject::TextObject::Around),
3565− 'i' => select_textobject(cx, textobject::TextObject::Inside),
3566− _ => (),
3567− }
3568− }
3569− })
3613+fn select_textobject_around(cx: &mut Context) {
3614+ select_textobject(cx, textobject::TextObject::Around);
3615+}
3616+
3617+fn select_textobject_inner(cx: &mut Context) {
3618+ select_textobject(cx, textobject::TextObject::Inside);
35703619}
35713620
35723621fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
@@ -3784,7 +3833,7 @@ mode_info! {
37843833}
37853834
37863835mode_info! {
3787− /// goto mode
3836+ /// goto
37883837 ///
37893838 /// When specified with a count, it will go to that line without entering the mode.
37903839 goto_mode, GOTO_MODE, goto_prehook,
@@ -3815,3 +3864,68 @@ mode_info! {
38153864 /// last accessed file
38163865 "a" => goto_last_accessed_file,
38173866}
3867+
3868+mode_info! {
3869+ /// window
3870+ window_mode, WINDOW_MODE,
3871+ /// rotate
3872+ "w" | "C-w" => rotate_view,
3873+ /// horizontal split
3874+ "h" => hsplit,
3875+ /// vertical split
3876+ "v" => vsplit,
3877+ /// close
3878+ "q" => wclose,
3879+}
3880+
3881+mode_info! {
3882+ /// match
3883+ match_mode, MATCH_MODE,
3884+ /// matching character
3885+ "m" => match_brackets,
3886+ /// surround add
3887+ "s" => surround_add,
3888+ /// surround replace
3889+ "r" => surround_replace,
3890+ /// surround delete
3891+ "d" => surround_delete,
3892+ /// around object
3893+ "a" => select_textobject_around,
3894+ /// inside object
3895+ "i" => select_textobject_inner,
3896+}
3897+
3898+mode_info! {
3899+ /// select to previous
3900+ left_bracket_mode, LEFT_BRACKET_MODE,
3901+ /// previous diagnostic
3902+ "d" => goto_prev_diag,
3903+ /// diagnostic (first)
3904+ "D" => goto_first_diag,
3905+}
3906+
3907+mode_info! {
3908+ /// select to next
3909+ right_bracket_mode, RIGHT_BRACKET_MODE,
3910+ /// diagnostic
3911+ "d" => goto_next_diag,
3912+ /// diagnostic (last)
3913+ "D" => goto_last_diag,
3914+}
3915+
3916+mode_info! {
3917+ /// view
3918+ view_mode, VIEW_MODE,
3919+ /// align view top
3920+ "t" => align_view_top,
3921+ /// align view center
3922+ "z" | "c" => align_view_center,
3923+ /// align view bottom
3924+ "b" => align_view_bottom,
3925+ /// align view middle
3926+ "m" => align_view_middle,
3927+ /// scroll up
3928+ "k" => scroll_up,
3929+ /// scroll down
3930+ "j" => scroll_down,
3931+}