@@ -6,8 +6,8 @@ use helix_core::{
6 object, pos_at_coords,
6 object, pos_at_coords,
7 regex::{self, Regex},
7 regex::{self, Regex},
8 register::Register,
8 register::Register,
9− search, selection, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection,
9+ search, selection, surround, textobject, LineEnding, Position, Range, Rope, RopeGraphemes,
10− SmallVec, Tendril, Transaction,
10+ RopeSlice, Selection, SmallVec, Tendril, Transaction,
13use helix_view::{
13use helix_view::{
@@ -20,7 +20,7 @@ use helix_view::{
20 Document, DocumentId, Editor, ViewId,
20 Document, DocumentId, Editor, ViewId,
23−use anyhow::anyhow;
23+use anyhow::{anyhow, bail, Context as _};
24use helix_lsp::{
24use helix_lsp::{
26 util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
26 util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
@@ -37,6 +37,7 @@ use crate::{
37use crate::job::{self, Job, Jobs};
37use crate::job::{self, Job, Jobs};
38use futures_util::{FutureExt, TryFutureExt};
38use futures_util::{FutureExt, TryFutureExt};
39use std::collections::HashMap;
39use std::collections::HashMap;
40+use std::num::NonZeroUsize;
40use std::{fmt, future::Future};
41use std::{fmt, future::Future};
@@ -49,7 +50,7 @@ use serde::de::{self, Deserialize, Deserializer};
50pub struct Context<'a> {
51pub struct Context<'a> {
51 pub selected_register: helix_view::RegisterSelection,
52 pub selected_register: helix_view::RegisterSelection,
52− pub count: Option<std::num::NonZeroUsize>,
53+ pub count: Option<NonZeroUsize>,
53 pub editor: &'a mut Editor,
54 pub editor: &'a mut Editor,
55 pub callback: Option<crate::compositor::Callback>,
56 pub callback: Option<crate::compositor::Callback>,
@@ -75,7 +76,9 @@ impl<'a> Context<'a> {
77 pub fn on_next_key_mode(&mut self, map: HashMap<KeyEvent, fn(&mut Context)>) {
78 pub fn on_next_key_mode(&mut self, map: HashMap<KeyEvent, fn(&mut Context)>) {
79+ let count = self.count;
78 self.on_next_key(move |cx, event| {
80 self.on_next_key(move |cx, event| {
79 cx.editor.autoinfo = None;
82 cx.editor.autoinfo = None;
80 if let Some(func) = map.get(&event) {
83 if let Some(func) = map.get(&event) {
@@ -180,6 +183,9 @@ impl Command {
180 extend_till_prev_char,
183 extend_till_prev_char,
181 extend_prev_char,
184 extend_prev_char,
187+ switch_to_uppercase,
188+ switch_to_lowercase,
184 page_down,
190 page_down,
185 half_page_up,
191 half_page_up,
@@ -778,6 +784,57 @@ fn replace(cx: &mut Context) {
787+fn switch_case(cx: &mut Context) {
788+ let (view, doc) = current!(cx.editor);
790+ Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
791+ let text: Tendril = range
792+ .fragment(doc.text().slice(..))
795+ if ch.is_lowercase() {
796+ ch.to_uppercase().collect()
797+ } else if ch.is_uppercase() {
798+ ch.to_lowercase().collect()
805+ (range.from(), range.to() + 1, Some(text))
808+ doc.apply(&transaction, view.id);
809+ doc.append_changes_to_history(view.id);
812+fn switch_to_uppercase(cx: &mut Context) {
813+ let (view, doc) = current!(cx.editor);
815+ Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
816+ let text: Tendril = range.fragment(doc.text().slice(..)).to_uppercase().into();
818+ (range.from(), range.to() + 1, Some(text))
821+ doc.apply(&transaction, view.id);
822+ doc.append_changes_to_history(view.id);
825+fn switch_to_lowercase(cx: &mut Context) {
826+ let (view, doc) = current!(cx.editor);
828+ Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
829+ let text: Tendril = range.fragment(doc.text().slice(..)).to_lowercase().into();
831+ (range.from(), range.to() + 1, Some(text))
834+ doc.apply(&transaction, view.id);
835+ doc.append_changes_to_history(view.id);
781fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
838fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
782 use Direction::*;
839 use Direction::*;
783 let (view, doc) = current!(cx.editor);
840 let (view, doc) = current!(cx.editor);
@@ -1194,34 +1251,45 @@ mod cmd {
1194 pub alias: Option<&'static str>,
1251 pub alias: Option<&'static str>,
1195 pub doc: &'static str,
1252 pub doc: &'static str,
1196 // params, flags, helper, completer
1253 // params, flags, helper, completer
1197− pub fun: fn(&mut compositor::Context, &[&str], PromptEvent),
1254+ pub fun: fn(&mut compositor::Context, &[&str], PromptEvent) -> anyhow::Result<()>,
1198 pub completer: Option<Completer>,
1255 pub completer: Option<Completer>,
1201− fn quit(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1259+ cx: &mut compositor::Context,
1261+ _event: PromptEvent,
1262+ ) -> anyhow::Result<()> {
1202 // last view and we have unsaved changes
1263 // last view and we have unsaved changes
1203− if cx.editor.tree.views().count() == 1 && buffers_remaining_impl(cx.editor) {
1264+ if cx.editor.tree.views().count() == 1 {
1204− return;
1265+ buffers_remaining_impl(cx.editor)?
1206 cx.editor
1268 cx.editor
1207 .close(view!(cx.editor).id, /* close_buffer */ false);
1269 .close(view!(cx.editor).id, /* close_buffer */ false);
1210− fn force_quit(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1275+ cx: &mut compositor::Context,
1277+ _event: PromptEvent,
1278+ ) -> anyhow::Result<()> {
1211 cx.editor
1279 cx.editor
1212 .close(view!(cx.editor).id, /* close_buffer */ false);
1280 .close(view!(cx.editor).id, /* close_buffer */ false);
1215− fn open(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1286+ cx: &mut compositor::Context,
1216− match args.get(0) {
1287+ args: &[&str],
1217− Some(path) => {
1288+ _event: PromptEvent,
1218− // TODO: handle error
1289+ ) -> anyhow::Result<()> {
1290+ let path = args.get(0).context("wrong argument count")?;
1219− let _ = cx.editor.open(path.into(), Action::Replace);
1291+ let _ = cx.editor.open(path.into(), Action::Replace)?;
1222− cx.editor.set_error("wrong argument count".to_string());
1227 fn write_impl<P: AsRef<Path>>(
1295 fn write_impl<P: AsRef<Path>>(
@@ -1232,12 +1300,10 @@ mod cmd {
1232 let (_, doc) = current!(cx.editor);
1300 let (_, doc) = current!(cx.editor);
1234 if let Some(path) = path {
1302 if let Some(path) = path {
1235− if let Err(err) = doc.set_path(path.as_ref()) {
1303+ doc.set_path(path.as_ref()).context("invalid filepath")?;
1236− return Err(anyhow!("invalid filepath: {}", err));
1239 if doc.path().is_none() {
1305 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");
1242 let fmt = doc.auto_format().map(|fmt| {
1308 let fmt = doc.auto_format().map(|fmt| {
1243 let shared = fmt.shared();
1309 let shared = fmt.shared();
@@ -1253,21 +1319,33 @@ mod cmd {
1253 Ok(tokio::spawn(doc.format_and_save(fmt)))
1319 Ok(tokio::spawn(doc.format_and_save(fmt)))
1256− fn write(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1323+ cx: &mut compositor::Context,
1325+ _event: PromptEvent,
1326+ ) -> anyhow::Result<()> {
1257− match write_impl(cx, args.first()) {
1327+ let handle = write_impl(cx, args.first())?;
1258− Err(e) => cx.editor.set_error(e.to_string()),
1260− cx.jobs
1328+ cx.jobs
1261− .add(Job::new(handle.unwrap_or_else(|e| Err(e.into()))).wait_before_exiting());
1329+ .add(Job::new(handle.unwrap_or_else(|e| Err(e.into()))).wait_before_exiting());
1266− fn new_file(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1335+ cx: &mut compositor::Context,
1337+ _event: PromptEvent,
1338+ ) -> anyhow::Result<()> {
1267 cx.editor.new_file(Action::Replace);
1339 cx.editor.new_file(Action::Replace);
1270− fn format(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1345+ cx: &mut compositor::Context,
1347+ _event: PromptEvent,
1348+ ) -> anyhow::Result<()> {
1271 let (_, doc) = current!(cx.editor);
1349 let (_, doc) = current!(cx.editor);
1273 if let Some(format) = doc.format() {
1351 if let Some(format) = doc.format() {
@@ -1275,9 +1353,14 @@ mod cmd {
1275 make_format_callback(doc.id(), doc.version(), Modified::LeaveModified, format);
1353 make_format_callback(doc.id(), doc.version(), Modified::LeaveModified, format);
1276 cx.jobs.callback(callback);
1354 cx.jobs.callback(callback);
1359+ fn set_indent_style(
1280− fn set_indent_style(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1360+ cx: &mut compositor::Context,
1362+ _event: PromptEvent,
1363+ ) -> anyhow::Result<()> {
1281 use IndentStyle::*;
1364 use IndentStyle::*;
1283 // If no argument, report current indent style.
1366 // If no argument, report current indent style.
@@ -1289,7 +1372,7 @@ mod cmd {
1289 Spaces(n) if (2..=8).contains(&n) => format!("{} spaces", n),
1372 Spaces(n) if (2..=8).contains(&n) => format!("{} spaces", n),
1290 _ => "error".into(), // Shouldn't happen.
1373 _ => "error".into(), // Shouldn't happen.
1292− return;
1375+ return Ok(());
1295 // Attempt to parse argument as an indent style.
1378 // Attempt to parse argument as an indent style.
@@ -1304,18 +1387,19 @@ mod cmd {
1304 _ => None,
1387 _ => None,
1307− if let Some(s) = style {
1308− let doc = doc_mut!(cx.editor);
1309− doc.indent_style = s;
1311− // Invalid argument.
1313− .set_error(format!("invalid indent style '{}'", args[0],));
1390+ let style = style.context("invalid indent style")?;
1314− }
1391+ let doc = doc_mut!(cx.editor);
1392+ doc.indent_style = style;
1317 /// Sets or reports the current document's line ending setting.
1397 /// Sets or reports the current document's line ending setting.
1398+ fn set_line_ending(
1318− fn set_line_ending(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1399+ cx: &mut compositor::Context,
1401+ _event: PromptEvent,
1402+ ) -> anyhow::Result<()> {
1319 use LineEnding::*;
1403 use LineEnding::*;
1321 // If no argument, report current line ending setting.
1405 // If no argument, report current line ending setting.
@@ -1331,7 +1415,8 @@ mod cmd {
1331 // These should never be a document's default line ending.
1415 // These should never be a document's default line ending.
1332 VT | LS | PS => "error".into(),
1416 VT | LS | PS => "error".into(),
1334− return;
1419+ return Ok(());
1337 // Attempt to parse argument as a line ending.
1422 // Attempt to parse argument as a line ending.
@@ -1345,72 +1430,65 @@ mod cmd {
1345 _ => None,
1430 _ => None,
1348− if let Some(le) = line_ending {
1433+ let line_ending = line_ending.context("invalid line ending")?;
1349− doc_mut!(cx.editor).line_ending = le;
1434+ doc_mut!(cx.editor).line_ending = line_ending;
1350− } else {
1435+ Ok(())
1351− // Invalid argument.
1353− .set_error(format!("invalid line ending '{}'", args[0],));
1357− fn earlier(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1439+ cx: &mut compositor::Context,
1441+ _event: PromptEvent,
1442+ ) -> anyhow::Result<()> {
1358− let uk = match args.join(" ").parse::<helix_core::history::UndoKind>() {
1445+ .parse::<helix_core::history::UndoKind>()
1359− Ok(uk) => uk,
1446+ .map_err(|s| anyhow!(s))?;
1361− cx.editor.set_error(msg);
1365 let (view, doc) = current!(cx.editor);
1448 let (view, doc) = current!(cx.editor);
1366− doc.earlier(view.id, uk)
1449+ doc.earlier(view.id, uk);
1369− fn later(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1455+ cx: &mut compositor::Context,
1457+ _event: PromptEvent,
1458+ ) -> anyhow::Result<()> {
1370− let uk = match args.join(" ").parse::<helix_core::history::UndoKind>() {
1461+ .parse::<helix_core::history::UndoKind>()
1371− Ok(uk) => uk,
1462+ .map_err(|s| anyhow!(s))?;
1373− cx.editor.set_error(msg);
1377 let (view, doc) = current!(cx.editor);
1463 let (view, doc) = current!(cx.editor);
1378− doc.later(view.id, uk)
1464+ doc.later(view.id, uk);
1381− fn write_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1470+ cx: &mut compositor::Context,
1472+ event: PromptEvent,
1473+ ) -> anyhow::Result<()> {
1382− match write_impl(cx, args.first()) {
1474+ let handle = write_impl(cx, args.first())?;
1384− if let Err(e) = helix_lsp::block_on(handle) {
1475+ let _ = helix_lsp::block_on(handle)?;
1385− cx.editor.set_error(e.to_string());
1387− quit(cx, &[], event);
1476+ quit(cx, &[], event)
1391− cx.editor.set_error(e.to_string());
1479+ fn force_write_quit(
1396− fn force_write_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1480+ cx: &mut compositor::Context,
1482+ event: PromptEvent,
1483+ ) -> anyhow::Result<()> {
1397− match write_impl(cx, args.first()) {
1484+ let handle = write_impl(cx, args.first())?;
1399− if let Err(e) = helix_lsp::block_on(handle) {
1485+ let _ = helix_lsp::block_on(handle)?;
1400− cx.editor.set_error(e.to_string());
1402− force_quit(cx, &[], event);
1486+ force_quit(cx, &[], event)
1406− cx.editor.set_error(e.to_string());
1411− /// Returns `true` if there are modified buffers remaining and sets editor error,
1489+ /// Results an error if there are modified buffers remaining and sets editor error,
1412− /// otherwise returns `false`
1490+ /// otherwise returns `Ok(())`
1413− fn buffers_remaining_impl(editor: &mut Editor) -> bool {
1491+ fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()> {
1414 let modified: Vec<_> = editor
1492 let modified: Vec<_> = editor
1415 .documents()
1493 .documents()
1416 .filter(|doc| doc.is_modified())
1494 .filter(|doc| doc.is_modified())
@@ -1421,16 +1499,13 @@ mod cmd {
1422 .collect();
1500 .collect();
1423 if !modified.is_empty() {
1501 if !modified.is_empty() {
1424− let err = format!(
1502+ bail!(
1425 "{} unsaved buffer(s) remaining: {:?}",
1503 "{} unsaved buffer(s) remaining: {:?}",
1426 modified.len(),
1504 modified.len(),
1427 modified
1505 modified
1429− editor.set_error(err);
1436 fn write_all_impl(
1511 fn write_all_impl(
@@ -1439,7 +1514,7 @@ mod cmd {
1439 _event: PromptEvent,
1514 _event: PromptEvent,
1440 quit: bool,
1515 quit: bool,
1441 force: bool,
1516 force: bool,
1442− ) {
1517+ ) -> anyhow::Result<()> {
1443 let mut errors = String::new();
1518 let mut errors = String::new();
1445 // save all documents
1520 // save all documents
@@ -1452,11 +1527,10 @@ mod cmd {
1452 // TODO: handle error.
1527 // TODO: handle error.
1453 let _ = helix_lsp::block_on(tokio::spawn(doc.save()));
1528 let _ = helix_lsp::block_on(tokio::spawn(doc.save()));
1455− editor.set_error(errors);
1457 if quit {
1531 if quit {
1458− if !force && buffers_remaining_impl(editor) {
1533+ buffers_remaining_impl(editor)?;
1462 // close all views
1536 // close all views
@@ -1465,23 +1539,42 @@ mod cmd {
1465 editor.close(view_id, false);
1539 editor.close(view_id, false);
1470− fn write_all(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1547+ cx: &mut compositor::Context,
1549+ event: PromptEvent,
1550+ ) -> anyhow::Result<()> {
1471 write_all_impl(&mut cx.editor, args, event, false, false)
1551 write_all_impl(&mut cx.editor, args, event, false, false)
1474− fn write_all_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1555+ cx: &mut compositor::Context,
1557+ event: PromptEvent,
1558+ ) -> anyhow::Result<()> {
1475 write_all_impl(&mut cx.editor, args, event, true, false)
1559 write_all_impl(&mut cx.editor, args, event, true, false)
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,
1565+ event: PromptEvent,
1566+ ) -> anyhow::Result<()> {
1479 write_all_impl(&mut cx.editor, args, event, true, true)
1567 write_all_impl(&mut cx.editor, args, event, true, true)
1571+ editor: &mut Editor,
1482− fn quit_all_impl(editor: &mut Editor, _args: &[&str], _event: PromptEvent, force: bool) {
1573+ _event: PromptEvent,
1575+ ) -> anyhow::Result<()> {
1483− if !force && buffers_remaining_impl(editor) {
1577+ buffers_remaining_impl(editor)?;
1487 // close all views
1580 // close all views
@@ -1489,57 +1582,77 @@ mod cmd {
1489 for view_id in views {
1582 for view_id in views {
1490 editor.close(view_id, false);
1583 editor.close(view_id, false);
1494− fn quit_all(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1590+ cx: &mut compositor::Context,
1592+ event: PromptEvent,
1593+ ) -> anyhow::Result<()> {
1495 quit_all_impl(&mut cx.editor, args, event, false)
1594 quit_all_impl(&mut cx.editor, args, event, false)
1498− fn force_quit_all(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1598+ cx: &mut compositor::Context,
1600+ event: PromptEvent,
1601+ ) -> anyhow::Result<()> {
1499 quit_all_impl(&mut cx.editor, args, event, true)
1602 quit_all_impl(&mut cx.editor, args, event, true)
1502− fn theme(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1606+ cx: &mut compositor::Context,
1608+ _event: PromptEvent,
1609+ ) -> anyhow::Result<()> {
1503− let theme = if let Some(theme) = args.first() {
1610+ let theme = args.first().context("theme not provided")?;
1506− cx.editor.set_error("theme name not provided".into());
1510− cx.editor.set_theme_from_name(theme);
1611+ cx.editor.set_theme_from_name(theme)
1513 fn yank_main_selection_to_clipboard(
1614 fn yank_main_selection_to_clipboard(
1514 cx: &mut compositor::Context,
1615 cx: &mut compositor::Context,
1515 _args: &[&str],
1616 _args: &[&str],
1516 _event: PromptEvent,
1617 _event: PromptEvent,
1517− ) {
1618+ ) -> anyhow::Result<()> {
1518− yank_main_selection_to_clipboard_impl(&mut cx.editor);
1619+ yank_main_selection_to_clipboard_impl(&mut cx.editor)
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,
1625+ _event: PromptEvent,
1626+ ) -> anyhow::Result<()> {
1522 let (_, doc) = current!(cx.editor);
1627 let (_, doc) = current!(cx.editor);
1523 let separator = args
1628 let separator = args
1524 .first()
1629 .first()
1525 .copied()
1630 .copied()
1526 .unwrap_or_else(|| doc.line_ending.as_str());
1631 .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)
1530− fn paste_clipboard_after(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1635+ fn paste_clipboard_after(
1636+ cx: &mut compositor::Context,
1638+ _event: PromptEvent,
1639+ ) -> anyhow::Result<()> {
1531− paste_clipboard_impl(&mut cx.editor, Paste::After);
1640+ paste_clipboard_impl(&mut cx.editor, Paste::After)
1534− fn paste_clipboard_before(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1643+ fn paste_clipboard_before(
1644+ cx: &mut compositor::Context,
1646+ _event: PromptEvent,
1647+ ) -> anyhow::Result<()> {
1535− paste_clipboard_impl(&mut cx.editor, Paste::After);
1648+ paste_clipboard_impl(&mut cx.editor, Paste::After)
1538 fn replace_selections_with_clipboard(
1651 fn replace_selections_with_clipboard(
1539 cx: &mut compositor::Context,
1652 cx: &mut compositor::Context,
1540 _args: &[&str],
1653 _args: &[&str],
1541 _event: PromptEvent,
1654 _event: PromptEvent,
1542− ) {
1655+ ) -> anyhow::Result<()> {
1543 let (view, doc) = current!(cx.editor);
1656 let (view, doc) = current!(cx.editor);
1545 match cx.editor.clipboard_provider.get_contents() {
1658 match cx.editor.clipboard_provider.get_contents() {
@@ -1555,71 +1668,76 @@ mod cmd {
1556 doc.apply(&transaction, view.id);
1669 doc.apply(&transaction, view.id);
1557 doc.append_changes_to_history(view.id);
1670 doc.append_changes_to_history(view.id);
1559− Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e),
1673+ Err(e) => Err(e.context("Couldn't get system clipboard contents")),
1563− fn show_clipboard_provider(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1677+ fn show_clipboard_provider(
1678+ cx: &mut compositor::Context,
1680+ _event: PromptEvent,
1681+ ) -> anyhow::Result<()> {
1564 cx.editor
1682 cx.editor
1565 .set_status(cx.editor.clipboard_provider.name().into());
1683 .set_status(cx.editor.clipboard_provider.name().into());
1568− fn change_current_directory(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) {
1687+ fn change_current_directory(
1569− let dir = match args.first() {
1688+ cx: &mut compositor::Context,
1570− Some(dir) => dir,
1689+ args: &[&str],
1571− None => {
1690+ _event: PromptEvent,
1691+ ) -> anyhow::Result<()> {
1572− cx.editor.set_error("target directory not provided".into());
1692+ let dir = args.first().context("target directory not provided")?;
1577 if let Err(e) = std::env::set_current_dir(dir) {
1694 if let Err(e) = std::env::set_current_dir(dir) {
1578− cx.editor.set_error(format!(
1579− "Couldn't change the current working directory: {:?}",
1695+ bail!("Couldn't change the current working directory: {:?}", e);
1585− match std::env::current_dir() {
1586− Ok(cwd) => cx.editor.set_status(format!(
1587− "Current working directory is now {}",
1592− .set_error(format!("Couldn't get the new working directory: {}", e)),
1698+ let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
1593− }
1699+ cx.editor.set_status(format!(
1700+ "Current working directory is now {}",
1596− fn show_current_directory(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) {
1706+ fn show_current_directory(
1707+ cx: &mut compositor::Context,
1709+ _event: PromptEvent,
1710+ ) -> anyhow::Result<()> {
1597− match std::env::current_dir() {
1711+ let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
1598− Ok(cwd) => cx
1712+ cx.editor
1599− .editor
1713+ .set_status(format!("Current working directory is {}", cwd.display()));
1600− .set_status(format!("Current working directory is {}", cwd.display())),
1714+ Ok(())
1603− .set_error(format!("Couldn't get the current working directory: {}", e)),
1607 /// Sets the [`Document`]'s encoding..
1717 /// Sets the [`Document`]'s encoding..
1608− fn set_encoding(cx: &mut compositor::Context, args: &[&str], _: PromptEvent) {
1719+ cx: &mut compositor::Context,
1721+ _event: PromptEvent,
1722+ ) -> anyhow::Result<()> {
1609 let (_, doc) = current!(cx.editor);
1723 let (_, doc) = current!(cx.editor);
1610 if let Some(label) = args.first() {
1724 if let Some(label) = args.first() {
1611 doc.set_encoding(label)
1725 doc.set_encoding(label)
1612− .unwrap_or_else(|e| cx.editor.set_error(e.to_string()));
1613 } else {
1726 } else {
1614 let encoding = doc.encoding().name().to_string();
1727 let encoding = doc.encoding().name().to_string();
1615− cx.editor.set_status(encoding)
1728+ cx.editor.set_status(encoding);
1619 /// Reload the [`Document`] from its source file.
1733 /// Reload the [`Document`] from its source file.
1620− fn reload(cx: &mut compositor::Context, _args: &[&str], _: PromptEvent) {
1735+ cx: &mut compositor::Context,
1737+ _event: PromptEvent,
1738+ ) -> anyhow::Result<()> {
1621 let (view, doc) = current!(cx.editor);
1739 let (view, doc) = current!(cx.editor);
1622− doc.reload(view.id).unwrap();
1740+ doc.reload(view.id)
1625 pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
1743 pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
@@ -1884,7 +2002,9 @@ fn command_mode(cx: &mut Context) {
1886 if let Some(cmd) = cmd::COMMANDS.get(parts[0]) {
2004 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));
1888 } else {
2008 } else {
1889 cx.editor
2009 cx.editor
1890 .set_error(format!("no such command: '{}'", parts[0]));
2010 .set_error(format!("no such command: '{}'", parts[0]));
@@ -2816,7 +2936,7 @@ fn yank(cx: &mut Context) {
2816 cx.editor.set_status(msg)
2936 cx.editor.set_status(msg)
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<()> {
2820 let (view, doc) = current!(editor);
2940 let (view, doc) = current!(editor);
2822 let values: Vec<String> = doc
2942 let values: Vec<String> = doc
@@ -2832,19 +2952,22 @@ fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) {
2833 let joined = values.join(separator);
2953 let joined = values.join(separator);
2835− if let Err(e) = editor.clipboard_provider.set_contents(joined) {
2956+ .clipboard_provider
2957+ .set_contents(joined)
2836− log::error!("Couldn't set system clipboard content: {:?}", e);
2958+ .context("Couldn't set system clipboard content")?;
2839 editor.set_status(msg);
2960 editor.set_status(msg);
2842fn yank_joined_to_clipboard(cx: &mut Context) {
2965fn yank_joined_to_clipboard(cx: &mut Context) {
2843 let line_ending = current!(cx.editor).1.line_ending;
2966 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());
2847−fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) {
2970+fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> {
2848 let (view, doc) = current!(editor);
2971 let (view, doc) = current!(editor);
2850 let value = doc
2973 let value = doc
@@ -2853,14 +2976,15 @@ fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) {
2853 .fragment(doc.text().slice(..));
2976 .fragment(doc.text().slice(..));
2855 if let Err(e) = editor.clipboard_provider.set_contents(value.into_owned()) {
2978 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);
2859 editor.set_status("yanked main selection to system clipboard".to_owned());
2982 editor.set_status("yanked main selection to system clipboard".to_owned());
2862fn yank_main_selection_to_clipboard(cx: &mut Context) {
2986fn 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);
2866#[derive(Copy, Clone)]
2990#[derive(Copy, Clone)]
@@ -2909,7 +3033,7 @@ fn paste_impl(
2909 Some(transaction)
3033 Some(transaction)
2912−fn paste_clipboard_impl(editor: &mut Editor, action: Paste) {
3036+fn paste_clipboard_impl(editor: &mut Editor, action: Paste) -> anyhow::Result<()> {
2913 let (view, doc) = current!(editor);
3037 let (view, doc) = current!(editor);
2915 match editor
3039 match editor
@@ -2920,18 +3044,19 @@ fn paste_clipboard_impl(editor: &mut Editor, action: Paste) {
2920 Ok(Some(transaction)) => {
3044 Ok(Some(transaction)) => {
2921 doc.apply(&transaction, view.id);
3045 doc.apply(&transaction, view.id);
2922 doc.append_changes_to_history(view.id);
3046 doc.append_changes_to_history(view.id);
2924− Ok(None) => {}
3049+ Ok(None) => Ok(()),
2925− Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e),
3050+ Err(e) => Err(e.context("Couldn't get system clipboard contents")),
2929fn paste_clipboard_after(cx: &mut Context) {
3054fn 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);
2933fn paste_clipboard_before(cx: &mut Context) {
3058fn 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);
2937fn replace_with_yanked(cx: &mut Context) {
3062fn replace_with_yanked(cx: &mut Context) {
@@ -2959,7 +3084,7 @@ fn replace_with_yanked(cx: &mut Context) {
2962−fn replace_selections_with_clipboard_impl(editor: &mut Editor) {
3087+fn replace_selections_with_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> {
2963 let (view, doc) = current!(editor);
3088 let (view, doc) = current!(editor);
2965 match editor.clipboard_provider.get_contents() {
3090 match editor.clipboard_provider.get_contents() {
@@ -2974,13 +3099,14 @@ fn replace_selections_with_clipboard_impl(editor: &mut Editor) {
2975 doc.apply(&transaction, view.id);
3100 doc.apply(&transaction, view.id);
2976 doc.append_changes_to_history(view.id);
3101 doc.append_changes_to_history(view.id);
2978− Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e),
3104+ Err(e) => Err(e.context("Couldn't get system clipboard contents")),
2982fn replace_selections_with_clipboard(cx: &mut Context) {
3108fn 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);
2986fn paste_after(cx: &mut Context) {
3112fn paste_after(cx: &mut Context) {
@@ -3405,24 +3531,6 @@ fn jump_backward(cx: &mut Context) {
3408−fn window_mode(cx: &mut Context) {
3409− cx.on_next_key(move |cx, event| {
3411− code: KeyCode::Char(ch),
3416− 'w' => rotate_view(cx),
3426fn rotate_view(cx: &mut Context) {
3534fn rotate_view(cx: &mut Context) {
3427 cx.editor.focus_next()
3535 cx.editor.focus_next()
@@ -3468,105 +3576,46 @@ fn select_register(cx: &mut Context) {
3471−fn view_mode(cx: &mut Context) {
3579+fn align_view_top(cx: &mut Context) {
3472− cx.on_next_key(move |cx, event| {
3474− code: KeyCode::Char(ch),
3478− // if lock, call cx again
3479− // TODO: temporarily show VIE in the mode list
3487− let (view, doc) = current!(cx.editor);
3580+ let (view, doc) = current!(cx.editor);
3581+ align_view(doc, view, Align::Top);
3489− align_view(doc, view, match ch {
3584+fn align_view_center(cx: &mut Context) {
3490− 'z' | 'c' => Align::Center,
3492− 'b' => Align::Bottom,
3493− _ => unreachable!()
3497− let (view, doc) = current!(cx.editor);
3585+ let (view, doc) = current!(cx.editor);
3498− let pos = doc.selection(view.id).cursor();
3586+ align_view(doc, view, Align::Center);
3499− let pos = coords_at_pos(doc.text().slice(..), pos);
3587+}
3501− const OFFSET: usize = 7; // gutters
3589+fn align_view_bottom(cx: &mut Context) {
3502− view.first_col = pos.col.saturating_sub(((view.area.width as usize).saturating_sub(OFFSET)) / 2);
3590+ let (view, doc) = current!(cx.editor);
3503− },
3591+ align_view(doc, view, Align::Bottom);
3505− 'j' => scroll(cx, 1, Direction::Forward),
3506− 'k' => scroll(cx, 1, Direction::Backward),
3514−fn left_bracket_mode(cx: &mut Context) {
3594+fn align_view_middle(cx: &mut Context) {
3515− cx.on_next_key(move |cx, event| {
3595+ let (view, doc) = current!(cx.editor);
3516− if let KeyEvent {
3596+ let pos = doc.selection(view.id).cursor();
3517− code: KeyCode::Char(ch),
3597+ let pos = coords_at_pos(doc.text().slice(..), pos);
3519− } = event
3599+ const OFFSET: usize = 7; // gutters
3520− {
3600+ view.first_col = pos
3521− match ch {
3601+ .col
3522− 'd' => goto_prev_diag(cx),
3602+ .saturating_sub(((view.area.width as usize).saturating_sub(OFFSET)) / 2);
3523− 'D' => goto_first_diag(cx),
3530−fn right_bracket_mode(cx: &mut Context) {
3605+fn scroll_up(cx: &mut Context) {
3531− cx.on_next_key(move |cx, event| {
3606+ scroll(cx, cx.count(), Direction::Backward);
3533− code: KeyCode::Char(ch),
3538− 'd' => goto_next_diag(cx),
3539− 'D' => goto_last_diag(cx),
3546−use helix_core::surround;
3609+fn scroll_down(cx: &mut Context) {
3547−use helix_core::textobject;
3610+ scroll(cx, cx.count(), Direction::Forward);
3549−fn match_mode(cx: &mut Context) {
3613+fn select_textobject_around(cx: &mut Context) {
3550− let count = cx.count;
3551− cx.on_next_key(move |cx, event| {
3553− code: KeyCode::Char(ch),
3557− // FIXME: count gets reset because of cx.on_next_key()
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),
3614+ select_textobject(cx, textobject::TextObject::Around);
3617+fn select_textobject_inner(cx: &mut Context) {
3565− 'i' => select_textobject(cx, textobject::TextObject::Inside),
3618+ select_textobject(cx, textobject::TextObject::Inside);
3572fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
3621fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
@@ -3784,7 +3833,7 @@ mode_info! {
3786mode_info! {
3835mode_info! {
3787− /// goto mode
3836+ /// goto
3789 /// When specified with a count, it will go to that line without entering the mode.
3838 /// When specified with a count, it will go to that line without entering the mode.
3790 goto_mode, GOTO_MODE, goto_prehook,
3839 goto_mode, GOTO_MODE, goto_prehook,
@@ -3815,3 +3864,68 @@ mode_info! {
3815 /// last accessed file
3864 /// last accessed file
3816 "a" => goto_last_accessed_file,
3865 "a" => goto_last_accessed_file,
3870+ window_mode, WINDOW_MODE,
3872+ "w" | "C-w" => rotate_view,
3873+ /// horizontal split
3883+ match_mode, MATCH_MODE,
3884+ /// matching character
3885+ "m" => match_brackets,
3887+ "s" => surround_add,
3888+ /// surround replace
3889+ "r" => surround_replace,
3890+ /// surround delete
3891+ "d" => surround_delete,
3893+ "a" => select_textobject_around,
3895+ "i" => select_textobject_inner,
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,
3909+ right_bracket_mode, RIGHT_BRACKET_MODE,
3911+ "d" => goto_next_diag,
3912+ /// diagnostic (last)
3913+ "D" => goto_last_diag,
3918+ view_mode, VIEW_MODE,
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,
3930+ "j" => scroll_down,