@@ -1,8 +1,10 @@
11pub(crate) mod dap;
22pub(crate) mod lsp;
3+pub(crate) mod typed;
34
45pub use dap::*;
56pub use lsp::*;
7+pub use typed::*;
68
79use helix_core::{
810 comment, coords_at_pos, find_first_non_whitespace_char, find_root, graphemes,
@@ -179,7 +181,7 @@ impl MappableCommand {
179181 match &self {
180182 Self::Typable { name, args, doc: _ } => {
181183 let args: Vec<Cow<str>> = args.iter().map(Cow::from).collect();
182− if let Some(command) = cmd::TYPABLE_COMMAND_MAP.get(name.as_str()) {
184+ if let Some(command) = typed::TYPABLE_COMMAND_MAP.get(name.as_str()) {
183185 let mut cx = compositor::Context {
184186 editor: cx.editor,
185187 jobs: cx.jobs,
@@ -459,7 +461,7 @@ impl std::str::FromStr for MappableCommand {
459461 let args = typable_command
460462 .map(|s| s.to_owned())
461463 .collect::<Vec<String>>();
462− cmd::TYPABLE_COMMAND_MAP
464+ typed::TYPABLE_COMMAND_MAP
463465 .get(name)
464466 .map(|cmd| MappableCommand::Typable {
465467 name: cmd.name.to_owned(),
@@ -2017,1473 +2019,6 @@ fn append_mode(cx: &mut Context) {
20172019 doc.set_selection(view.id, selection);
20182020}
20192021
2020−pub mod cmd {
2021− use super::*;
2022−
2023− use helix_view::editor::Action;
2024− use ui::completers::{self, Completer};
2025−
2026− #[derive(Clone)]
2027− pub struct TypableCommand {
2028− pub name: &'static str,
2029− pub aliases: &'static [&'static str],
2030− pub doc: &'static str,
2031− // params, flags, helper, completer
2032− pub fun: fn(&mut compositor::Context, &[Cow<str>], PromptEvent) -> anyhow::Result<()>,
2033− pub completer: Option<Completer>,
2034− }
2035−
2036− fn quit(
2037− cx: &mut compositor::Context,
2038− _args: &[Cow<str>],
2039− _event: PromptEvent,
2040− ) -> anyhow::Result<()> {
2041− // last view and we have unsaved changes
2042− if cx.editor.tree.views().count() == 1 {
2043− buffers_remaining_impl(cx.editor)?
2044− }
2045−
2046− cx.editor.close(view!(cx.editor).id);
2047−
2048− Ok(())
2049− }
2050−
2051− fn force_quit(
2052− cx: &mut compositor::Context,
2053− _args: &[Cow<str>],
2054− _event: PromptEvent,
2055− ) -> anyhow::Result<()> {
2056− cx.editor.close(view!(cx.editor).id);
2057−
2058− Ok(())
2059− }
2060−
2061− fn open(
2062− cx: &mut compositor::Context,
2063− args: &[Cow<str>],
2064− _event: PromptEvent,
2065− ) -> anyhow::Result<()> {
2066− ensure!(!args.is_empty(), "wrong argument count");
2067− for arg in args {
2068− let (path, pos) = args::parse_file(arg);
2069− let _ = cx.editor.open(path, Action::Replace)?;
2070− let (view, doc) = current!(cx.editor);
2071− let pos = Selection::point(pos_at_coords(doc.text().slice(..), pos, true));
2072− doc.set_selection(view.id, pos);
2073− // does not affect opening a buffer without pos
2074− align_view(doc, view, Align::Center);
2075− }
2076− Ok(())
2077− }
2078−
2079− fn buffer_close_by_ids_impl(
2080− editor: &mut Editor,
2081− doc_ids: &[DocumentId],
2082− force: bool,
2083− ) -> anyhow::Result<()> {
2084− for &doc_id in doc_ids {
2085− editor.close_document(doc_id, force)?;
2086− }
2087−
2088− Ok(())
2089− }
2090−
2091− fn buffer_gather_paths_impl(editor: &mut Editor, args: &[Cow<str>]) -> Vec<DocumentId> {
2092− // No arguments implies current document
2093− if args.is_empty() {
2094− let doc_id = view!(editor).doc;
2095− return vec![doc_id];
2096− }
2097−
2098− let mut nonexistent_buffers = vec![];
2099− let mut document_ids = vec![];
2100− for arg in args {
2101− let doc_id = editor.documents().find_map(|doc| {
2102− let arg_path = Some(Path::new(arg.as_ref()));
2103− if doc.path().map(|p| p.as_path()) == arg_path
2104− || doc.relative_path().as_deref() == arg_path
2105− {
2106− Some(doc.id())
2107− } else {
2108− None
2109− }
2110− });
2111−
2112− match doc_id {
2113− Some(doc_id) => document_ids.push(doc_id),
2114− None => nonexistent_buffers.push(format!("'{}'", arg)),
2115− }
2116− }
2117−
2118− if !nonexistent_buffers.is_empty() {
2119− editor.set_error(format!(
2120− "cannot close non-existent buffers: {}",
2121− nonexistent_buffers.join(", ")
2122− ));
2123− }
2124−
2125− document_ids
2126− }
2127−
2128− fn buffer_close(
2129− cx: &mut compositor::Context,
2130− args: &[Cow<str>],
2131− _event: PromptEvent,
2132− ) -> anyhow::Result<()> {
2133− let document_ids = buffer_gather_paths_impl(cx.editor, args);
2134− buffer_close_by_ids_impl(cx.editor, &document_ids, false)
2135− }
2136−
2137− fn force_buffer_close(
2138− cx: &mut compositor::Context,
2139− args: &[Cow<str>],
2140− _event: PromptEvent,
2141− ) -> anyhow::Result<()> {
2142− let document_ids = buffer_gather_paths_impl(cx.editor, args);
2143− buffer_close_by_ids_impl(cx.editor, &document_ids, true)
2144− }
2145−
2146− fn buffer_gather_others_impl(editor: &mut Editor) -> Vec<DocumentId> {
2147− let current_document = &doc!(editor).id();
2148− editor
2149− .documents()
2150− .map(|doc| doc.id())
2151− .filter(|doc_id| doc_id != current_document)
2152− .collect()
2153− }
2154−
2155− fn buffer_close_others(
2156− cx: &mut compositor::Context,
2157− _args: &[Cow<str>],
2158− _event: PromptEvent,
2159− ) -> anyhow::Result<()> {
2160− let document_ids = buffer_gather_others_impl(cx.editor);
2161− buffer_close_by_ids_impl(cx.editor, &document_ids, false)
2162− }
2163−
2164− fn force_buffer_close_others(
2165− cx: &mut compositor::Context,
2166− _args: &[Cow<str>],
2167− _event: PromptEvent,
2168− ) -> anyhow::Result<()> {
2169− let document_ids = buffer_gather_others_impl(cx.editor);
2170− buffer_close_by_ids_impl(cx.editor, &document_ids, true)
2171− }
2172−
2173− fn buffer_gather_all_impl(editor: &mut Editor) -> Vec<DocumentId> {
2174− editor.documents().map(|doc| doc.id()).collect()
2175− }
2176−
2177− fn buffer_close_all(
2178− cx: &mut compositor::Context,
2179− _args: &[Cow<str>],
2180− _event: PromptEvent,
2181− ) -> anyhow::Result<()> {
2182− let document_ids = buffer_gather_all_impl(cx.editor);
2183− buffer_close_by_ids_impl(cx.editor, &document_ids, false)
2184− }
2185−
2186− fn force_buffer_close_all(
2187− cx: &mut compositor::Context,
2188− _args: &[Cow<str>],
2189− _event: PromptEvent,
2190− ) -> anyhow::Result<()> {
2191− let document_ids = buffer_gather_all_impl(cx.editor);
2192− buffer_close_by_ids_impl(cx.editor, &document_ids, true)
2193− }
2194−
2195− fn write_impl(cx: &mut compositor::Context, path: Option<&Cow<str>>) -> anyhow::Result<()> {
2196− let jobs = &mut cx.jobs;
2197− let doc = doc_mut!(cx.editor);
2198−
2199− if let Some(ref path) = path {
2200− doc.set_path(Some(path.as_ref().as_ref()))
2201− .context("invalid filepath")?;
2202− }
2203− if doc.path().is_none() {
2204− bail!("cannot write a buffer without a filename");
2205− }
2206− let fmt = doc.auto_format().map(|fmt| {
2207− let shared = fmt.shared();
2208− let callback = make_format_callback(
2209− doc.id(),
2210− doc.version(),
2211− Modified::SetUnmodified,
2212− shared.clone(),
2213− );
2214− jobs.callback(callback);
2215− shared
2216− });
2217− let future = doc.format_and_save(fmt);
2218− cx.jobs.add(Job::new(future).wait_before_exiting());
2219−
2220− if path.is_some() {
2221− let id = doc.id();
2222− let _ = cx.editor.refresh_language_server(id);
2223− }
2224− Ok(())
2225− }
2226−
2227− fn write(
2228− cx: &mut compositor::Context,
2229− args: &[Cow<str>],
2230− _event: PromptEvent,
2231− ) -> anyhow::Result<()> {
2232− write_impl(cx, args.first())
2233− }
2234−
2235− fn new_file(
2236− cx: &mut compositor::Context,
2237− _args: &[Cow<str>],
2238− _event: PromptEvent,
2239− ) -> anyhow::Result<()> {
2240− cx.editor.new_file(Action::Replace);
2241−
2242− Ok(())
2243− }
2244−
2245− fn format(
2246− cx: &mut compositor::Context,
2247− _args: &[Cow<str>],
2248− _event: PromptEvent,
2249− ) -> anyhow::Result<()> {
2250− let doc = doc!(cx.editor);
2251− if let Some(format) = doc.format() {
2252− let callback =
2253− make_format_callback(doc.id(), doc.version(), Modified::LeaveModified, format);
2254− cx.jobs.callback(callback);
2255− }
2256−
2257− Ok(())
2258− }
2259− fn set_indent_style(
2260− cx: &mut compositor::Context,
2261− args: &[Cow<str>],
2262− _event: PromptEvent,
2263− ) -> anyhow::Result<()> {
2264− use IndentStyle::*;
2265−
2266− // If no argument, report current indent style.
2267− if args.is_empty() {
2268− let style = doc!(cx.editor).indent_style;
2269− cx.editor.set_status(match style {
2270− Tabs => "tabs".to_owned(),
2271− Spaces(1) => "1 space".to_owned(),
2272− Spaces(n) if (2..=8).contains(&n) => format!("{} spaces", n),
2273− _ => unreachable!(), // Shouldn't happen.
2274− });
2275− return Ok(());
2276− }
2277−
2278− // Attempt to parse argument as an indent style.
2279− let style = match args.get(0) {
2280− Some(arg) if "tabs".starts_with(&arg.to_lowercase()) => Some(Tabs),
2281− Some(Cow::Borrowed("0")) => Some(Tabs),
2282− Some(arg) => arg
2283− .parse::<u8>()
2284− .ok()
2285− .filter(|n| (1..=8).contains(n))
2286− .map(Spaces),
2287− _ => None,
2288− };
2289−
2290− let style = style.context("invalid indent style")?;
2291− let doc = doc_mut!(cx.editor);
2292− doc.indent_style = style;
2293−
2294− Ok(())
2295− }
2296−
2297− /// Sets or reports the current document's line ending setting.
2298− fn set_line_ending(
2299− cx: &mut compositor::Context,
2300− args: &[Cow<str>],
2301− _event: PromptEvent,
2302− ) -> anyhow::Result<()> {
2303− use LineEnding::*;
2304−
2305− // If no argument, report current line ending setting.
2306− if args.is_empty() {
2307− let line_ending = doc!(cx.editor).line_ending;
2308− cx.editor.set_status(match line_ending {
2309− Crlf => "crlf",
2310− LF => "line feed",
2311− FF => "form feed",
2312− CR => "carriage return",
2313− Nel => "next line",
2314−
2315− // These should never be a document's default line ending.
2316− VT | LS | PS => "error",
2317− });
2318−
2319− return Ok(());
2320− }
2321−
2322− let arg = args
2323− .get(0)
2324− .context("argument missing")?
2325− .to_ascii_lowercase();
2326−
2327− // Attempt to parse argument as a line ending.
2328− let line_ending = match arg {
2329− // We check for CR first because it shares a common prefix with CRLF.
2330− arg if arg.starts_with("cr") => CR,
2331− arg if arg.starts_with("crlf") => Crlf,
2332− arg if arg.starts_with("lf") => LF,
2333− arg if arg.starts_with("ff") => FF,
2334− arg if arg.starts_with("nel") => Nel,
2335− _ => bail!("invalid line ending"),
2336− };
2337−
2338− doc_mut!(cx.editor).line_ending = line_ending;
2339− Ok(())
2340− }
2341−
2342− fn earlier(
2343− cx: &mut compositor::Context,
2344− args: &[Cow<str>],
2345− _event: PromptEvent,
2346− ) -> anyhow::Result<()> {
2347− let uk = args.join(" ").parse::<UndoKind>().map_err(|s| anyhow!(s))?;
2348−
2349− let (view, doc) = current!(cx.editor);
2350− let success = doc.earlier(view.id, uk);
2351− if !success {
2352− cx.editor.set_status("Already at oldest change");
2353− }
2354−
2355− Ok(())
2356− }
2357−
2358− fn later(
2359− cx: &mut compositor::Context,
2360− args: &[Cow<str>],
2361− _event: PromptEvent,
2362− ) -> anyhow::Result<()> {
2363− let uk = args.join(" ").parse::<UndoKind>().map_err(|s| anyhow!(s))?;
2364− let (view, doc) = current!(cx.editor);
2365− let success = doc.later(view.id, uk);
2366− if !success {
2367− cx.editor.set_status("Already at newest change");
2368− }
2369−
2370− Ok(())
2371− }
2372−
2373− fn write_quit(
2374− cx: &mut compositor::Context,
2375− args: &[Cow<str>],
2376− event: PromptEvent,
2377− ) -> anyhow::Result<()> {
2378− write_impl(cx, args.first())?;
2379− quit(cx, &[], event)
2380− }
2381−
2382− fn force_write_quit(
2383− cx: &mut compositor::Context,
2384− args: &[Cow<str>],
2385− event: PromptEvent,
2386− ) -> anyhow::Result<()> {
2387− write_impl(cx, args.first())?;
2388− force_quit(cx, &[], event)
2389− }
2390−
2391− /// Results an error if there are modified buffers remaining and sets editor error,
2392− /// otherwise returns `Ok(())`
2393− pub(super) fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()> {
2394− let modified: Vec<_> = editor
2395− .documents()
2396− .filter(|doc| doc.is_modified())
2397− .map(|doc| {
2398− doc.relative_path()
2399− .map(|path| path.to_string_lossy().to_string())
2400− .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into())
2401− })
2402− .collect();
2403− if !modified.is_empty() {
2404− bail!(
2405− "{} unsaved buffer(s) remaining: {:?}",
2406− modified.len(),
2407− modified
2408− );
2409− }
2410− Ok(())
2411− }
2412−
2413− fn write_all_impl(
2414− cx: &mut compositor::Context,
2415− _args: &[Cow<str>],
2416− _event: PromptEvent,
2417− quit: bool,
2418− force: bool,
2419− ) -> anyhow::Result<()> {
2420− let mut errors = String::new();
2421− let jobs = &mut cx.jobs;
2422− // save all documents
2423− for doc in &mut cx.editor.documents.values_mut() {
2424− if doc.path().is_none() {
2425− errors.push_str("cannot write a buffer without a filename\n");
2426− continue;
2427− }
2428−
2429− if !doc.is_modified() {
2430− continue;
2431− }
2432−
2433− let fmt = doc.auto_format().map(|fmt| {
2434− let shared = fmt.shared();
2435− let callback = make_format_callback(
2436− doc.id(),
2437− doc.version(),
2438− Modified::SetUnmodified,
2439− shared.clone(),
2440− );
2441− jobs.callback(callback);
2442− shared
2443− });
2444− let future = doc.format_and_save(fmt);
2445− jobs.add(Job::new(future).wait_before_exiting());
2446− }
2447−
2448− if quit {
2449− if !force {
2450− buffers_remaining_impl(cx.editor)?;
2451− }
2452−
2453− // close all views
2454− let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect();
2455− for view_id in views {
2456− cx.editor.close(view_id);
2457− }
2458− }
2459−
2460− bail!(errors)
2461− }
2462−
2463− fn write_all(
2464− cx: &mut compositor::Context,
2465− args: &[Cow<str>],
2466− event: PromptEvent,
2467− ) -> anyhow::Result<()> {
2468− write_all_impl(cx, args, event, false, false)
2469− }
2470−
2471− fn write_all_quit(
2472− cx: &mut compositor::Context,
2473− args: &[Cow<str>],
2474− event: PromptEvent,
2475− ) -> anyhow::Result<()> {
2476− write_all_impl(cx, args, event, true, false)
2477− }
2478−
2479− fn force_write_all_quit(
2480− cx: &mut compositor::Context,
2481− args: &[Cow<str>],
2482− event: PromptEvent,
2483− ) -> anyhow::Result<()> {
2484− write_all_impl(cx, args, event, true, true)
2485− }
2486−
2487− fn quit_all_impl(editor: &mut Editor, force: bool) -> anyhow::Result<()> {
2488− if !force {
2489− buffers_remaining_impl(editor)?;
2490− }
2491−
2492− // close all views
2493− let views: Vec<_> = editor.tree.views().map(|(view, _)| view.id).collect();
2494− for view_id in views {
2495− editor.close(view_id);
2496− }
2497−
2498− Ok(())
2499− }
2500−
2501− fn quit_all(
2502− cx: &mut compositor::Context,
2503− _args: &[Cow<str>],
2504− _event: PromptEvent,
2505− ) -> anyhow::Result<()> {
2506− quit_all_impl(cx.editor, false)
2507− }
2508−
2509− fn force_quit_all(
2510− cx: &mut compositor::Context,
2511− _args: &[Cow<str>],
2512− _event: PromptEvent,
2513− ) -> anyhow::Result<()> {
2514− quit_all_impl(cx.editor, true)
2515− }
2516−
2517− fn cquit(
2518− cx: &mut compositor::Context,
2519− args: &[Cow<str>],
2520− _event: PromptEvent,
2521− ) -> anyhow::Result<()> {
2522− let exit_code = args
2523− .first()
2524− .and_then(|code| code.parse::<i32>().ok())
2525− .unwrap_or(1);
2526− cx.editor.exit_code = exit_code;
2527−
2528− quit_all_impl(cx.editor, false)
2529− }
2530−
2531− fn force_cquit(
2532− cx: &mut compositor::Context,
2533− args: &[Cow<str>],
2534− _event: PromptEvent,
2535− ) -> anyhow::Result<()> {
2536− let exit_code = args
2537− .first()
2538− .and_then(|code| code.parse::<i32>().ok())
2539− .unwrap_or(1);
2540− cx.editor.exit_code = exit_code;
2541−
2542− quit_all_impl(cx.editor, true)
2543− }
2544−
2545− fn theme(
2546− cx: &mut compositor::Context,
2547− args: &[Cow<str>],
2548− _event: PromptEvent,
2549− ) -> anyhow::Result<()> {
2550− let theme = args.first().context("Theme not provided")?;
2551− let theme = cx
2552− .editor
2553− .theme_loader
2554− .load(theme)
2555− .with_context(|| format!("Failed setting theme {}", theme))?;
2556− let true_color = cx.editor.config.true_color || crate::true_color();
2557− if !(true_color || theme.is_16_color()) {
2558− bail!("Unsupported theme: theme requires true color support");
2559− }
2560− cx.editor.set_theme(theme);
2561− Ok(())
2562− }
2563−
2564− fn yank_main_selection_to_clipboard(
2565− cx: &mut compositor::Context,
2566− _args: &[Cow<str>],
2567− _event: PromptEvent,
2568− ) -> anyhow::Result<()> {
2569− yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Clipboard)
2570− }
2571−
2572− fn yank_joined_to_clipboard(
2573− cx: &mut compositor::Context,
2574− args: &[Cow<str>],
2575− _event: PromptEvent,
2576− ) -> anyhow::Result<()> {
2577− let doc = doc!(cx.editor);
2578− let default_sep = Cow::Borrowed(doc.line_ending.as_str());
2579− let separator = args.first().unwrap_or(&default_sep);
2580− yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Clipboard)
2581− }
2582−
2583− fn yank_main_selection_to_primary_clipboard(
2584− cx: &mut compositor::Context,
2585− _args: &[Cow<str>],
2586− _event: PromptEvent,
2587− ) -> anyhow::Result<()> {
2588− yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection)
2589− }
2590−
2591− fn yank_joined_to_primary_clipboard(
2592− cx: &mut compositor::Context,
2593− args: &[Cow<str>],
2594− _event: PromptEvent,
2595− ) -> anyhow::Result<()> {
2596− let doc = doc!(cx.editor);
2597− let default_sep = Cow::Borrowed(doc.line_ending.as_str());
2598− let separator = args.first().unwrap_or(&default_sep);
2599− yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Selection)
2600− }
2601−
2602− fn paste_clipboard_after(
2603− cx: &mut compositor::Context,
2604− _args: &[Cow<str>],
2605− _event: PromptEvent,
2606− ) -> anyhow::Result<()> {
2607− paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Clipboard, 1)
2608− }
2609−
2610− fn paste_clipboard_before(
2611− cx: &mut compositor::Context,
2612− _args: &[Cow<str>],
2613− _event: PromptEvent,
2614− ) -> anyhow::Result<()> {
2615− paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Clipboard, 1)
2616− }
2617−
2618− fn paste_primary_clipboard_after(
2619− cx: &mut compositor::Context,
2620− _args: &[Cow<str>],
2621− _event: PromptEvent,
2622− ) -> anyhow::Result<()> {
2623− paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Selection, 1)
2624− }
2625−
2626− fn paste_primary_clipboard_before(
2627− cx: &mut compositor::Context,
2628− _args: &[Cow<str>],
2629− _event: PromptEvent,
2630− ) -> anyhow::Result<()> {
2631− paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Selection, 1)
2632− }
2633−
2634− fn replace_selections_with_clipboard_impl(
2635− cx: &mut compositor::Context,
2636− clipboard_type: ClipboardType,
2637− ) -> anyhow::Result<()> {
2638− let (view, doc) = current!(cx.editor);
2639−
2640− match cx.editor.clipboard_provider.get_contents(clipboard_type) {
2641− Ok(contents) => {
2642− let selection = doc.selection(view.id);
2643− let transaction =
2644− Transaction::change_by_selection(doc.text(), selection, |range| {
2645− (range.from(), range.to(), Some(contents.as_str().into()))
2646− });
2647−
2648− doc.apply(&transaction, view.id);
2649− doc.append_changes_to_history(view.id);
2650− Ok(())
2651− }
2652− Err(e) => Err(e.context("Couldn't get system clipboard contents")),
2653− }
2654− }
2655−
2656− fn replace_selections_with_clipboard(
2657− cx: &mut compositor::Context,
2658− _args: &[Cow<str>],
2659− _event: PromptEvent,
2660− ) -> anyhow::Result<()> {
2661− replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard)
2662− }
2663−
2664− fn replace_selections_with_primary_clipboard(
2665− cx: &mut compositor::Context,
2666− _args: &[Cow<str>],
2667− _event: PromptEvent,
2668− ) -> anyhow::Result<()> {
2669− replace_selections_with_clipboard_impl(cx, ClipboardType::Selection)
2670− }
2671−
2672− fn show_clipboard_provider(
2673− cx: &mut compositor::Context,
2674− _args: &[Cow<str>],
2675− _event: PromptEvent,
2676− ) -> anyhow::Result<()> {
2677− cx.editor
2678− .set_status(cx.editor.clipboard_provider.name().to_string());
2679− Ok(())
2680− }
2681−
2682− fn change_current_directory(
2683− cx: &mut compositor::Context,
2684− args: &[Cow<str>],
2685− _event: PromptEvent,
2686− ) -> anyhow::Result<()> {
2687− let dir = helix_core::path::expand_tilde(
2688− args.first()
2689− .context("target directory not provided")?
2690− .as_ref()
2691− .as_ref(),
2692− );
2693−
2694− if let Err(e) = std::env::set_current_dir(dir) {
2695− bail!("Couldn't change the current working directory: {}", e);
2696− }
2697−
2698− let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
2699− cx.editor.set_status(format!(
2700− "Current working directory is now {}",
2701− cwd.display()
2702− ));
2703− Ok(())
2704− }
2705−
2706− fn show_current_directory(
2707− cx: &mut compositor::Context,
2708− _args: &[Cow<str>],
2709− _event: PromptEvent,
2710− ) -> anyhow::Result<()> {
2711− let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
2712− cx.editor
2713− .set_status(format!("Current working directory is {}", cwd.display()));
2714− Ok(())
2715− }
2716−
2717− /// Sets the [`Document`]'s encoding..
2718− fn set_encoding(
2719− cx: &mut compositor::Context,
2720− args: &[Cow<str>],
2721− _event: PromptEvent,
2722− ) -> anyhow::Result<()> {
2723− let doc = doc_mut!(cx.editor);
2724− if let Some(label) = args.first() {
2725− doc.set_encoding(label)
2726− } else {
2727− let encoding = doc.encoding().name().to_owned();
2728− cx.editor.set_status(encoding);
2729− Ok(())
2730− }
2731− }
2732−
2733− /// Reload the [`Document`] from its source file.
2734− fn reload(
2735− cx: &mut compositor::Context,
2736− _args: &[Cow<str>],
2737− _event: PromptEvent,
2738− ) -> anyhow::Result<()> {
2739− let (view, doc) = current!(cx.editor);
2740− doc.reload(view.id)
2741− }
2742−
2743− fn tree_sitter_scopes(
2744− cx: &mut compositor::Context,
2745− _args: &[Cow<str>],
2746− _event: PromptEvent,
2747− ) -> anyhow::Result<()> {
2748− let (view, doc) = current!(cx.editor);
2749− let text = doc.text().slice(..);
2750−
2751− let pos = doc.selection(view.id).primary().cursor(text);
2752− let scopes = indent::get_scopes(doc.syntax(), text, pos);
2753− cx.editor.set_status(format!("scopes: {:?}", &scopes));
2754− Ok(())
2755− }
2756−
2757− fn vsplit(
2758− cx: &mut compositor::Context,
2759− args: &[Cow<str>],
2760− _event: PromptEvent,
2761− ) -> anyhow::Result<()> {
2762− let id = view!(cx.editor).doc;
2763−
2764− if args.is_empty() {
2765− cx.editor.switch(id, Action::VerticalSplit);
2766− } else {
2767− for arg in args {
2768− cx.editor
2769− .open(PathBuf::from(arg.as_ref()), Action::VerticalSplit)?;
2770− }
2771− }
2772−
2773− Ok(())
2774− }
2775−
2776− fn hsplit(
2777− cx: &mut compositor::Context,
2778− args: &[Cow<str>],
2779− _event: PromptEvent,
2780− ) -> anyhow::Result<()> {
2781− let id = view!(cx.editor).doc;
2782−
2783− if args.is_empty() {
2784− cx.editor.switch(id, Action::HorizontalSplit);
2785− } else {
2786− for arg in args {
2787− cx.editor
2788− .open(PathBuf::from(arg.as_ref()), Action::HorizontalSplit)?;
2789− }
2790− }
2791−
2792− Ok(())
2793− }
2794−
2795− fn debug_eval(
2796− cx: &mut compositor::Context,
2797− args: &[Cow<str>],
2798− _event: PromptEvent,
2799− ) -> anyhow::Result<()> {
2800− if let Some(debugger) = cx.editor.debugger.as_mut() {
2801− let (frame, thread_id) = match (debugger.active_frame, debugger.thread_id) {
2802− (Some(frame), Some(thread_id)) => (frame, thread_id),
2803− _ => {
2804− bail!("Cannot find current stack frame to access variables")
2805− }
2806− };
2807−
2808− // TODO: support no frame_id
2809−
2810− let frame_id = debugger.stack_frames[&thread_id][frame].id;
2811− let response = helix_lsp::block_on(debugger.eval(args.join(" "), Some(frame_id)))?;
2812− cx.editor.set_status(response.result);
2813− }
2814− Ok(())
2815− }
2816−
2817− fn debug_start(
2818− cx: &mut compositor::Context,
2819− args: &[Cow<str>],
2820− _event: PromptEvent,
2821− ) -> anyhow::Result<()> {
2822− let mut args = args.to_owned();
2823− let name = match args.len() {
2824− 0 => None,
2825− _ => Some(args.remove(0)),
2826− };
2827− dap_start_impl(cx, name.as_deref(), None, Some(args))
2828− }
2829−
2830− fn debug_remote(
2831− cx: &mut compositor::Context,
2832− args: &[Cow<str>],
2833− _event: PromptEvent,
2834− ) -> anyhow::Result<()> {
2835− let mut args = args.to_owned();
2836− let address = match args.len() {
2837− 0 => None,
2838− _ => Some(args.remove(0).parse()?),
2839− };
2840− let name = match args.len() {
2841− 0 => None,
2842− _ => Some(args.remove(0)),
2843− };
2844− dap_start_impl(cx, name.as_deref(), address, Some(args))
2845− }
2846−
2847− fn tutor(
2848− cx: &mut compositor::Context,
2849− _args: &[Cow<str>],
2850− _event: PromptEvent,
2851− ) -> anyhow::Result<()> {
2852− let path = helix_core::runtime_dir().join("tutor.txt");
2853− cx.editor.open(path, Action::Replace)?;
2854− // Unset path to prevent accidentally saving to the original tutor file.
2855− doc_mut!(cx.editor).set_path(None)?;
2856− Ok(())
2857− }
2858−
2859− pub(super) fn goto_line_number(
2860− cx: &mut compositor::Context,
2861− args: &[Cow<str>],
2862− _event: PromptEvent,
2863− ) -> anyhow::Result<()> {
2864− ensure!(!args.is_empty(), "Line number required");
2865−
2866− let line = args[0].parse::<usize>()?;
2867−
2868− goto_line_impl(cx.editor, NonZeroUsize::new(line));
2869−
2870− let (view, doc) = current!(cx.editor);
2871−
2872− view.ensure_cursor_in_view(doc, line);
2873− Ok(())
2874− }
2875−
2876− fn setting(
2877− cx: &mut compositor::Context,
2878− args: &[Cow<str>],
2879− _event: PromptEvent,
2880− ) -> anyhow::Result<()> {
2881− let runtime_config = &mut cx.editor.config;
2882−
2883− if args.len() != 2 {
2884− anyhow::bail!("Bad arguments. Usage: `:set key field`");
2885− }
2886−
2887− let (key, arg) = (&args[0].to_lowercase(), &args[1]);
2888−
2889− match key.as_ref() {
2890− "scrolloff" => runtime_config.scrolloff = arg.parse()?,
2891− "scroll-lines" => runtime_config.scroll_lines = arg.parse()?,
2892− "mouse" => runtime_config.mouse = arg.parse()?,
2893− "line-number" => runtime_config.line_number = arg.parse()?,
2894− "middle-click_paste" => runtime_config.middle_click_paste = arg.parse()?,
2895− "auto-pairs" => runtime_config.auto_pairs = arg.parse()?,
2896− "auto-completion" => runtime_config.auto_completion = arg.parse()?,
2897− "completion-trigger-len" => runtime_config.completion_trigger_len = arg.parse()?,
2898− "auto-info" => runtime_config.auto_info = arg.parse()?,
2899− "true-color" => runtime_config.true_color = arg.parse()?,
2900− "search.smart-case" => runtime_config.search.smart_case = arg.parse()?,
2901− "search.wrap-around" => runtime_config.search.wrap_around = arg.parse()?,
2902− _ => anyhow::bail!("Unknown key `{}`.", args[0]),
2903− }
2904−
2905− Ok(())
2906− }
2907−
2908− fn sort(
2909− cx: &mut compositor::Context,
2910− args: &[Cow<str>],
2911− _event: PromptEvent,
2912− ) -> anyhow::Result<()> {
2913− sort_impl(cx, args, false)
2914− }
2915−
2916− fn sort_reverse(
2917− cx: &mut compositor::Context,
2918− args: &[Cow<str>],
2919− _event: PromptEvent,
2920− ) -> anyhow::Result<()> {
2921− sort_impl(cx, args, true)
2922− }
2923−
2924− fn sort_impl(
2925− cx: &mut compositor::Context,
2926− _args: &[Cow<str>],
2927− reverse: bool,
2928− ) -> anyhow::Result<()> {
2929− let (view, doc) = current!(cx.editor);
2930− let text = doc.text().slice(..);
2931−
2932− let selection = doc.selection(view.id);
2933−
2934− let mut fragments: Vec<_> = selection
2935− .fragments(text)
2936− .map(|fragment| Tendril::from(fragment.as_ref()))
2937− .collect();
2938−
2939− fragments.sort_by(match reverse {
2940− true => |a: &Tendril, b: &Tendril| b.cmp(a),
2941− false => |a: &Tendril, b: &Tendril| a.cmp(b),
2942− });
2943−
2944− let transaction = Transaction::change(
2945− doc.text(),
2946− selection
2947− .into_iter()
2948− .zip(fragments)
2949− .map(|(s, fragment)| (s.from(), s.to(), Some(fragment))),
2950− );
2951−
2952− doc.apply(&transaction, view.id);
2953− doc.append_changes_to_history(view.id);
2954−
2955− Ok(())
2956− }
2957−
2958− fn tree_sitter_subtree(
2959− cx: &mut compositor::Context,
2960− _args: &[Cow<str>],
2961− _event: PromptEvent,
2962− ) -> anyhow::Result<()> {
2963− let (view, doc) = current!(cx.editor);
2964−
2965− if let Some(syntax) = doc.syntax() {
2966− let primary_selection = doc.selection(view.id).primary();
2967− let text = doc.text();
2968− let from = text.char_to_byte(primary_selection.from());
2969− let to = text.char_to_byte(primary_selection.to());
2970− if let Some(selected_node) = syntax
2971− .tree()
2972− .root_node()
2973− .descendant_for_byte_range(from, to)
2974− {
2975− let contents = format!("```tsq\n{}\n```", selected_node.to_sexp());
2976−
2977− let callback = async move {
2978− let call: job::Callback =
2979− Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
2980− let contents = ui::Markdown::new(contents, editor.syn_loader.clone());
2981− let popup = Popup::new("hover", contents).auto_close(true);
2982− compositor.replace_or_push("hover", popup);
2983− });
2984− Ok(call)
2985− };
2986−
2987− cx.jobs.callback(callback);
2988− }
2989− }
2990−
2991− Ok(())
2992− }
2993−
2994− pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
2995− TypableCommand {
2996− name: "quit",
2997− aliases: &["q"],
2998− doc: "Close the current view.",
2999− fun: quit,
3000− completer: None,
3001− },
3002− TypableCommand {
3003− name: "quit!",
3004− aliases: &["q!"],
3005− doc: "Close the current view forcefully (ignoring unsaved changes).",
3006− fun: force_quit,
3007− completer: None,
3008− },
3009− TypableCommand {
3010− name: "open",
3011− aliases: &["o"],
3012− doc: "Open a file from disk into the current view.",
3013− fun: open,
3014− completer: Some(completers::filename),
3015− },
3016− TypableCommand {
3017− name: "buffer-close",
3018− aliases: &["bc", "bclose"],
3019− doc: "Close the current buffer.",
3020− fun: buffer_close,
3021− completer: Some(completers::buffer),
3022− },
3023− TypableCommand {
3024− name: "buffer-close!",
3025− aliases: &["bc!", "bclose!"],
3026− doc: "Close the current buffer forcefully (ignoring unsaved changes).",
3027− fun: force_buffer_close,
3028− completer: Some(completers::buffer),
3029− },
3030− TypableCommand {
3031− name: "buffer-close-others",
3032− aliases: &["bco", "bcloseother"],
3033− doc: "Close all buffers but the currently focused one.",
3034− fun: buffer_close_others,
3035− completer: None,
3036− },
3037− TypableCommand {
3038− name: "buffer-close-others!",
3039− aliases: &["bco!", "bcloseother!"],
3040− doc: "Close all buffers but the currently focused one.",
3041− fun: force_buffer_close_others,
3042− completer: None,
3043− },
3044− TypableCommand {
3045− name: "buffer-close-all",
3046− aliases: &["bca", "bcloseall"],
3047− doc: "Close all buffers, without quiting.",
3048− fun: buffer_close_all,
3049− completer: None,
3050− },
3051− TypableCommand {
3052− name: "buffer-close-all!",
3053− aliases: &["bca!", "bcloseall!"],
3054− doc: "Close all buffers forcefully (ignoring unsaved changes), without quiting.",
3055− fun: force_buffer_close_all,
3056− completer: None,
3057− },
3058− TypableCommand {
3059− name: "write",
3060− aliases: &["w"],
3061− doc: "Write changes to disk. Accepts an optional path (:write some/path.txt)",
3062− fun: write,
3063− completer: Some(completers::filename),
3064− },
3065− TypableCommand {
3066− name: "new",
3067− aliases: &["n"],
3068− doc: "Create a new scratch buffer.",
3069− fun: new_file,
3070− completer: Some(completers::filename),
3071− },
3072− TypableCommand {
3073− name: "format",
3074− aliases: &["fmt"],
3075− doc: "Format the file using the LSP formatter.",
3076− fun: format,
3077− completer: None,
3078− },
3079− TypableCommand {
3080− name: "indent-style",
3081− aliases: &[],
3082− doc: "Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.)",
3083− fun: set_indent_style,
3084− completer: None,
3085− },
3086− TypableCommand {
3087− name: "line-ending",
3088− aliases: &[],
3089− doc: "Set the document's default line ending. Options: crlf, lf, cr, ff, nel.",
3090− fun: set_line_ending,
3091− completer: None,
3092− },
3093− TypableCommand {
3094− name: "earlier",
3095− aliases: &["ear"],
3096− doc: "Jump back to an earlier point in edit history. Accepts a number of steps or a time span.",
3097− fun: earlier,
3098− completer: None,
3099− },
3100− TypableCommand {
3101− name: "later",
3102− aliases: &["lat"],
3103− doc: "Jump to a later point in edit history. Accepts a number of steps or a time span.",
3104− fun: later,
3105− completer: None,
3106− },
3107− TypableCommand {
3108− name: "write-quit",
3109− aliases: &["wq", "x"],
3110− doc: "Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt)",
3111− fun: write_quit,
3112− completer: Some(completers::filename),
3113− },
3114− TypableCommand {
3115− name: "write-quit!",
3116− aliases: &["wq!", "x!"],
3117− doc: "Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt)",
3118− fun: force_write_quit,
3119− completer: Some(completers::filename),
3120− },
3121− TypableCommand {
3122− name: "write-all",
3123− aliases: &["wa"],
3124− doc: "Write changes from all views to disk.",
3125− fun: write_all,
3126− completer: None,
3127− },
3128− TypableCommand {
3129− name: "write-quit-all",
3130− aliases: &["wqa", "xa"],
3131− doc: "Write changes from all views to disk and close all views.",
3132− fun: write_all_quit,
3133− completer: None,
3134− },
3135− TypableCommand {
3136− name: "write-quit-all!",
3137− aliases: &["wqa!", "xa!"],
3138− doc: "Write changes from all views to disk and close all views forcefully (ignoring unsaved changes).",
3139− fun: force_write_all_quit,
3140− completer: None,
3141− },
3142− TypableCommand {
3143− name: "quit-all",
3144− aliases: &["qa"],
3145− doc: "Close all views.",
3146− fun: quit_all,
3147− completer: None,
3148− },
3149− TypableCommand {
3150− name: "quit-all!",
3151− aliases: &["qa!"],
3152− doc: "Close all views forcefully (ignoring unsaved changes).",
3153− fun: force_quit_all,
3154− completer: None,
3155− },
3156− TypableCommand {
3157− name: "cquit",
3158− aliases: &["cq"],
3159− doc: "Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2).",
3160− fun: cquit,
3161− completer: None,
3162− },
3163− TypableCommand {
3164− name: "cquit!",
3165− aliases: &["cq!"],
3166− doc: "Quit with exit code (default 1) forcefully (ignoring unsaved changes). Accepts an optional integer exit code (:cq! 2).",
3167− fun: force_cquit,
3168− completer: None,
3169− },
3170− TypableCommand {
3171− name: "theme",
3172− aliases: &[],
3173− doc: "Change the editor theme.",
3174− fun: theme,
3175− completer: Some(completers::theme),
3176− },
3177− TypableCommand {
3178− name: "clipboard-yank",
3179− aliases: &[],
3180− doc: "Yank main selection into system clipboard.",
3181− fun: yank_main_selection_to_clipboard,
3182− completer: None,
3183− },
3184− TypableCommand {
3185− name: "clipboard-yank-join",
3186− aliases: &[],
3187− doc: "Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline.", // FIXME: current UI can't display long doc.
3188− fun: yank_joined_to_clipboard,
3189− completer: None,
3190− },
3191− TypableCommand {
3192− name: "primary-clipboard-yank",
3193− aliases: &[],
3194− doc: "Yank main selection into system primary clipboard.",
3195− fun: yank_main_selection_to_primary_clipboard,
3196− completer: None,
3197− },
3198− TypableCommand {
3199− name: "primary-clipboard-yank-join",
3200− aliases: &[],
3201− doc: "Yank joined selections into system primary clipboard. A separator can be provided as first argument. Default value is newline.", // FIXME: current UI can't display long doc.
3202− fun: yank_joined_to_primary_clipboard,
3203− completer: None,
3204− },
3205− TypableCommand {
3206− name: "clipboard-paste-after",
3207− aliases: &[],
3208− doc: "Paste system clipboard after selections.",
3209− fun: paste_clipboard_after,
3210− completer: None,
3211− },
3212− TypableCommand {
3213− name: "clipboard-paste-before",
3214− aliases: &[],
3215− doc: "Paste system clipboard before selections.",
3216− fun: paste_clipboard_before,
3217− completer: None,
3218− },
3219− TypableCommand {
3220− name: "clipboard-paste-replace",
3221− aliases: &[],
3222− doc: "Replace selections with content of system clipboard.",
3223− fun: replace_selections_with_clipboard,
3224− completer: None,
3225− },
3226− TypableCommand {
3227− name: "primary-clipboard-paste-after",
3228− aliases: &[],
3229− doc: "Paste primary clipboard after selections.",
3230− fun: paste_primary_clipboard_after,
3231− completer: None,
3232− },
3233− TypableCommand {
3234− name: "primary-clipboard-paste-before",
3235− aliases: &[],
3236− doc: "Paste primary clipboard before selections.",
3237− fun: paste_primary_clipboard_before,
3238− completer: None,
3239− },
3240− TypableCommand {
3241− name: "primary-clipboard-paste-replace",
3242− aliases: &[],
3243− doc: "Replace selections with content of system primary clipboard.",
3244− fun: replace_selections_with_primary_clipboard,
3245− completer: None,
3246− },
3247− TypableCommand {
3248− name: "show-clipboard-provider",
3249− aliases: &[],
3250− doc: "Show clipboard provider name in status bar.",
3251− fun: show_clipboard_provider,
3252− completer: None,
3253− },
3254− TypableCommand {
3255− name: "change-current-directory",
3256− aliases: &["cd"],
3257− doc: "Change the current working directory.",
3258− fun: change_current_directory,
3259− completer: Some(completers::directory),
3260− },
3261− TypableCommand {
3262− name: "show-directory",
3263− aliases: &["pwd"],
3264− doc: "Show the current working directory.",
3265− fun: show_current_directory,
3266− completer: None,
3267− },
3268− TypableCommand {
3269− name: "encoding",
3270− aliases: &[],
3271− doc: "Set encoding based on `https://encoding.spec.whatwg.org`",
3272− fun: set_encoding,
3273− completer: None,
3274− },
3275− TypableCommand {
3276− name: "reload",
3277− aliases: &[],
3278− doc: "Discard changes and reload from the source file.",
3279− fun: reload,
3280− completer: None,
3281− },
3282− TypableCommand {
3283− name: "tree-sitter-scopes",
3284− aliases: &[],
3285− doc: "Display tree sitter scopes, primarily for theming and development.",
3286− fun: tree_sitter_scopes,
3287− completer: None,
3288− },
3289− TypableCommand {
3290− name: "debug-start",
3291− aliases: &["dbg"],
3292− doc: "Start a debug session from a given template with given parameters.",
3293− fun: debug_start,
3294− completer: None,
3295− },
3296− TypableCommand {
3297− name: "debug-remote",
3298− aliases: &["dbg-tcp"],
3299− doc: "Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters.",
3300− fun: debug_remote,
3301− completer: None,
3302− },
3303− TypableCommand {
3304− name: "debug-eval",
3305− aliases: &[],
3306− doc: "Evaluate expression in current debug context.",
3307− fun: debug_eval,
3308− completer: None,
3309− },
3310− TypableCommand {
3311− name: "vsplit",
3312− aliases: &["vs"],
3313− doc: "Open the file in a vertical split.",
3314− fun: vsplit,
3315− completer: Some(completers::filename),
3316− },
3317− TypableCommand {
3318− name: "hsplit",
3319− aliases: &["hs", "sp"],
3320− doc: "Open the file in a horizontal split.",
3321− fun: hsplit,
3322− completer: Some(completers::filename),
3323− },
3324− TypableCommand {
3325− name: "tutor",
3326− aliases: &[],
3327− doc: "Open the tutorial.",
3328− fun: tutor,
3329− completer: None,
3330− },
3331− TypableCommand {
3332− name: "goto",
3333− aliases: &["g"],
3334− doc: "Go to line number.",
3335− fun: goto_line_number,
3336− completer: None,
3337− },
3338− TypableCommand {
3339− name: "set-option",
3340− aliases: &["set"],
3341− doc: "Set a config option at runtime",
3342− fun: setting,
3343− completer: Some(completers::setting),
3344− },
3345− TypableCommand {
3346− name: "sort",
3347− aliases: &[],
3348− doc: "Sort ranges in selection.",
3349− fun: sort,
3350− completer: None,
3351− },
3352− TypableCommand {
3353− name: "rsort",
3354− aliases: &[],
3355− doc: "Sort ranges in selection in reverse order.",
3356− fun: sort_reverse,
3357− completer: None,
3358− },
3359− TypableCommand {
3360− name: "tree-sitter-subtree",
3361− aliases: &["ts-subtree"],
3362− doc: "Display tree sitter subtree under cursor, primarily for debugging queries.",
3363− fun: tree_sitter_subtree,
3364− completer: None,
3365− },
3366− ];
3367−
3368− pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
3369− Lazy::new(|| {
3370− TYPABLE_COMMAND_LIST
3371− .iter()
3372− .flat_map(|cmd| {
3373− std::iter::once((cmd.name, cmd))
3374− .chain(cmd.aliases.iter().map(move |&alias| (alias, cmd)))
3375− })
3376− .collect()
3377− });
3378−}
3379−
3380−fn command_mode(cx: &mut Context) {
3381− let mut prompt = Prompt::new(
3382− ":".into(),
3383− Some(':'),
3384− |editor: &Editor, input: &str| {
3385− static FUZZY_MATCHER: Lazy<fuzzy_matcher::skim::SkimMatcherV2> =
3386− Lazy::new(fuzzy_matcher::skim::SkimMatcherV2::default);
3387−
3388− // we use .this over split_whitespace() because we care about empty segments
3389− let parts = input.split(' ').collect::<Vec<&str>>();
3390−
3391− // simple heuristic: if there's no just one part, complete command name.
3392− // if there's a space, per command completion kicks in.
3393− if parts.len() <= 1 {
3394− let mut matches: Vec<_> = cmd::TYPABLE_COMMAND_LIST
3395− .iter()
3396− .filter_map(|command| {
3397− FUZZY_MATCHER
3398− .fuzzy_match(command.name, input)
3399− .map(|score| (command.name, score))
3400− })
3401− .collect();
3402−
3403− matches.sort_unstable_by_key(|(_file, score)| std::cmp::Reverse(*score));
3404− matches
3405− .into_iter()
3406− .map(|(name, _)| (0.., name.into()))
3407− .collect()
3408− } else {
3409− let part = parts.last().unwrap();
3410−
3411− if let Some(cmd::TypableCommand {
3412− completer: Some(completer),
3413− ..
3414− }) = cmd::TYPABLE_COMMAND_MAP.get(parts[0])
3415− {
3416− completer(editor, part)
3417− .into_iter()
3418− .map(|(range, file)| {
3419− // offset ranges to input
3420− let offset = input.len() - part.len();
3421− let range = (range.start + offset)..;
3422− (range, file)
3423− })
3424− .collect()
3425− } else {
3426− Vec::new()
3427− }
3428− }
3429− }, // completion
3430− move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
3431− if event != PromptEvent::Validate {
3432− return;
3433− }
3434−
3435− let parts = input.split_whitespace().collect::<Vec<&str>>();
3436− if parts.is_empty() {
3437− return;
3438− }
3439−
3440− // If command is numeric, interpret as line number and go there.
3441− if parts.len() == 1 && parts[0].parse::<usize>().ok().is_some() {
3442− if let Err(e) = cmd::goto_line_number(cx, &[Cow::from(parts[0])], event) {
3443− cx.editor.set_error(format!("{}", e));
3444− }
3445− return;
3446− }
3447−
3448− // Handle typable commands
3449− if let Some(cmd) = cmd::TYPABLE_COMMAND_MAP.get(parts[0]) {
3450− let args = if cfg!(unix) {
3451− shellwords::shellwords(input)
3452− } else {
3453− // Windows doesn't support POSIX, so fallback for now
3454− parts
3455− .into_iter()
3456− .map(|part| part.into())
3457− .collect::<Vec<_>>()
3458− };
3459−
3460− if let Err(e) = (cmd.fun)(cx, &args[1..], event) {
3461− cx.editor.set_error(format!("{}", e));
3462− }
3463− } else {
3464− cx.editor
3465− .set_error(format!("no such command: '{}'", parts[0]));
3466− };
3467− },
3468− );
3469− prompt.doc_fn = Box::new(|input: &str| {
3470− let part = input.split(' ').next().unwrap_or_default();
3471−
3472− if let Some(cmd::TypableCommand { doc, aliases, .. }) = cmd::TYPABLE_COMMAND_MAP.get(part) {
3473− if aliases.is_empty() {
3474− return Some((*doc).into());
3475− }
3476− return Some(format!("{}\nAliases: {}", doc, aliases.join(", ")).into());
3477− }
3478−
3479− None
3480− });
3481−
3482− // Calculate initial completion
3483− prompt.recalculate_completion(cx.editor);
3484− cx.push_layer(Box::new(prompt));
3485−}
3486−
34872022fn file_picker(cx: &mut Context) {
34882023 // We don't specify language markers, root will be the root of the current git repo
34892024 let root = find_root(None, &[]).unwrap_or_else(|| PathBuf::from("./"));
@@ -3567,15 +2102,13 @@ pub fn command_palette(cx: &mut Context) {
35672102 compositor.find::<ui::EditorView>().unwrap().keymaps[&doc.mode].reverse_map();
35682103
35692104 let mut commands: Vec<MappableCommand> = MappableCommand::STATIC_COMMAND_LIST.into();
3570− commands.extend(
3571− cmd::TYPABLE_COMMAND_LIST
3572− .iter()
3573− .map(|cmd| MappableCommand::Typable {
3574− name: cmd.name.to_owned(),
3575− doc: cmd.doc.to_owned(),
3576− args: Vec::new(),
3577− }),
3578− );
2105+ commands.extend(typed::TYPABLE_COMMAND_LIST.iter().map(|cmd| {
2106+ MappableCommand::Typable {
2107+ name: cmd.name.to_owned(),
2108+ doc: cmd.doc.to_owned(),
2109+ args: Vec::new(),
2110+ }
2111+ }));
35792112
35802113 // formats key bindings, multiple bindings are comma separated,
35812114 // individual key presses are joined with `+`
@@ -5260,7 +3793,7 @@ fn vsplit(cx: &mut Context) {
52603793
52613794fn wclose(cx: &mut Context) {
52623795 if cx.editor.tree.views().count() == 1 {
5263− if let Err(err) = cmd::buffers_remaining_impl(cx.editor) {
3796+ if let Err(err) = typed::buffers_remaining_impl(cx.editor) {
52643797 cx.editor.set_error(err.to_string());
52653798 return;
52663799 }