From 596482436a30b2c767d66b1b566ac2a687d226f0 Mon Sep 17 00:00:00 2001 From: jess Date: Tue, 28 Apr 2026 22:55:27 -0700 Subject: [PATCH] Added more auto-pairs types, '', ""; Added toggles in the menu for which auto-pairs to complete. --- linux/src/app.rs | 6 +- linux/src/config.rs | 6 ++ src/AppDelegate.swift | 36 ++++++++++ src/AppState.swift | 2 +- src/ConfigManager.swift | 5 ++ src/IcedViewportView.swift | 5 ++ viewport/include/acord.h | 18 +++++ viewport/src/editor.rs | 141 ++++++++++++++++++++++++------------- viewport/src/lib.rs | 13 ++++ windows/src/app.rs | 23 ++++-- windows/src/config.rs | 11 +++ windows/src/menu.rs | 47 +++++++++++-- 12 files changed, 254 insertions(+), 59 deletions(-) diff --git a/linux/src/app.rs b/linux/src/app.rs index de5d3d4..02f3d27 100644 --- a/linux/src/app.rs +++ b/linux/src/app.rs @@ -13,6 +13,7 @@ use acord_viewport::{ viewport_create, viewport_destroy, viewport_render, viewport_resize, viewport_set_text, viewport_get_text, viewport_set_theme, viewport_set_lang, viewport_set_line_indicator, viewport_set_gutter_rainbow, + viewport_set_auto_pair_flags, viewport_send_command, viewport_free_string, ViewportHandle, }; @@ -60,6 +61,7 @@ impl App { let ind = CString::new(self.config.line_indicator()).unwrap(); viewport_set_line_indicator(self.handle, ind.as_ptr()); viewport_set_gutter_rainbow(self.handle, self.config.gutter_rainbow()); + viewport_set_auto_pair_flags(self.handle, self.config.auto_pair_flags()); } fn dispatch_menu(&mut self, action: MenuAction, event_loop: &ActiveEventLoop) { @@ -146,8 +148,8 @@ impl App { } fn new_note(&mut self) { - let empty = CString::new("").unwrap(); - viewport_set_text(self.handle, empty.as_ptr()); + let stub = CString::new("# ").unwrap(); + viewport_set_text(self.handle, stub.as_ptr()); if let Some(w) = &self.window { w.set_title("Acord"); } diff --git a/linux/src/config.rs b/linux/src/config.rs index 80f139d..a1ac1ea 100644 --- a/linux/src/config.rs +++ b/linux/src/config.rs @@ -55,6 +55,12 @@ impl Config { .map(PathBuf::from) .unwrap_or_else(|| config_dir().join("notes")) } + + pub fn auto_pair_flags(&self) -> u32 { + self.data.get("autoPairFlags") + .and_then(|s| s.parse().ok()) + .unwrap_or(63) + } } /// XDG-friendly config dir with `~/.acord` fallback for parity with the diff --git a/src/AppDelegate.swift b/src/AppDelegate.swift index b392cd0..104f475 100644 --- a/src/AppDelegate.swift +++ b/src/AppDelegate.swift @@ -257,10 +257,45 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation { formatItem.target = self menu.addItem(formatItem) + menu.addItem(.separator()) + menu.addItem(buildAutoPairMenu()) + item.submenu = menu return item } + private func buildAutoPairMenu() -> NSMenuItem { + let item = NSMenuItem(title: "Auto Pair", action: nil, keyEquivalent: "") + let menu = NSMenu(title: "Auto Pair") + let pairs: [(String, UInt32)] = [ + ("Parens ( )", 1), + ("Brackets [ ]", 2), + ("Braces { }", 4), + ("Single quotes ' '", 8), + ("Double quotes \" \"", 16), + ("Backticks ` `", 32), + ] + let flags = ConfigManager.shared.autoPairFlags + for (label, bit) in pairs { + let mi = NSMenuItem(title: label, action: #selector(toggleAutoPair(_:)), keyEquivalent: "") + mi.target = self + mi.tag = Int(bit) + mi.state = (flags & bit) != 0 ? .on : .off + menu.addItem(mi) + } + item.submenu = menu + return item + } + + @objc private func toggleAutoPair(_ sender: NSMenuItem) { + let bit = UInt32(sender.tag) + var flags = ConfigManager.shared.autoPairFlags + flags ^= bit + ConfigManager.shared.autoPairFlags = flags + sender.state = (flags & bit) != 0 ? .on : .off + viewport?.setAutoPairFlags(flags) + } + private func buildRenderMenu() -> NSMenuItem { let item = NSMenuItem() let menu = NSMenu(title: "Render") @@ -610,6 +645,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation { private func syncGutterPrefsToViewport() { viewport?.setLineIndicator(ConfigManager.shared.lineIndicatorMode) viewport?.setGutterRainbow(ConfigManager.shared.gutterRainbow) + viewport?.setAutoPairFlags(ConfigManager.shared.autoPairFlags) } @objc private func toggleBrowser() { diff --git a/src/AppState.swift b/src/AppState.swift index e19aad5..2aea83e 100644 --- a/src/AppState.swift +++ b/src/AppState.swift @@ -300,7 +300,7 @@ class AppState: ObservableObject { let id = bridge.newDocument() currentNoteID = id selectedNoteIDs = [id] - documentText = "" + documentText = "# " evalResults = [:] modified = false currentFileURL = nil diff --git a/src/ConfigManager.swift b/src/ConfigManager.swift index d925b8a..dfa2236 100644 --- a/src/ConfigManager.swift +++ b/src/ConfigManager.swift @@ -62,4 +62,9 @@ class ConfigManager { get { CGFloat(Double(config["zoomLevel"] ?? "0") ?? 0) } set { config["zoomLevel"] = String(Double(newValue)); save() } } + + var autoPairFlags: UInt32 { + get { UInt32(config["autoPairFlags"] ?? "63") ?? 63 } + set { config["autoPairFlags"] = String(newValue); save() } + } } diff --git a/src/IcedViewportView.swift b/src/IcedViewportView.swift index 3a84188..cb5fe4b 100644 --- a/src/IcedViewportView.swift +++ b/src/IcedViewportView.swift @@ -265,6 +265,11 @@ class IcedViewportView: NSView { viewport_set_gutter_rainbow(h, enabled) } + func setAutoPairFlags(_ flags: UInt32) { + guard let h = viewportHandle else { return } + viewport_set_auto_pair_flags(h, flags) + } + /// Returns 0 = Live, 1 = Editor, 2 = View. func renderMode() -> UInt32 { guard let h = viewportHandle else { return 0 } diff --git a/viewport/include/acord.h b/viewport/include/acord.h index 88ac8a0..a015323 100644 --- a/viewport/include/acord.h +++ b/viewport/include/acord.h @@ -13,6 +13,20 @@ #include #include +#define PAREN (1 << 0) + +#define BRACKET (1 << 1) + +#define BRACE (1 << 2) + +#define SINGLE (1 << 3) + +#define DOUBLE (1 << 4) + +#define BACKTICK (1 << 5) + +#define ALL (((((PAREN | BRACKET) | BRACE) | SINGLE) | DOUBLE) | BACKTICK) + #define BASE_BOOST 0.30 #define THRESHOLD_PX 6.0 @@ -71,6 +85,10 @@ void viewport_set_line_indicator(struct ViewportHandle *handle, const char *mode void viewport_set_gutter_rainbow(struct ViewportHandle *handle, bool enabled); +void viewport_set_auto_pair_flags(struct ViewportHandle *handle, uint32_t flags); + +uint32_t viewport_get_auto_pair_flags(void); + void viewport_send_command(struct ViewportHandle *handle, uint32_t command); /** diff --git a/viewport/src/editor.rs b/viewport/src/editor.rs index 96f4b1d..78a39e6 100644 --- a/viewport/src/editor.rs +++ b/viewport/src/editor.rs @@ -1,7 +1,35 @@ use std::collections::HashMap; use std::sync::Arc; +use std::sync::atomic::{AtomicU8, Ordering}; use std::time::Instant; +pub mod auto_pair { + use super::{AtomicU8, Ordering}; + + pub const PAREN: u8 = 1 << 0; + pub const BRACKET: u8 = 1 << 1; + pub const BRACE: u8 = 1 << 2; + pub const SINGLE: u8 = 1 << 3; + pub const DOUBLE: u8 = 1 << 4; + pub const BACKTICK: u8 = 1 << 5; + + pub const ALL: u8 = PAREN | BRACKET | BRACE | SINGLE | DOUBLE | BACKTICK; + + static FLAGS: AtomicU8 = AtomicU8::new(ALL); + + pub fn enabled(flag: u8) -> bool { + FLAGS.load(Ordering::Relaxed) & flag != 0 + } + + pub fn flags() -> u8 { + FLAGS.load(Ordering::Relaxed) + } + + pub fn set_flags(flags: u8) { + FLAGS.store(flags, Ordering::Relaxed); + } +} + use iced_wgpu::core::keyboard::{self, Modifiers}; use iced_wgpu::core::keyboard::key; use iced_wgpu::core::text::{Highlight, Wrapping}; @@ -502,20 +530,7 @@ fn md_style() -> markdown::Style { impl EditorState { pub fn new() -> Self { - let sample = concat!( - "# Block Compositor\n", - "Acord renders structured documents with mixed content.\n\n", - "## Data Table\n", - "| Name | Age | Role |\n", - "|-------|-----|----------|\n", - "| Alice | 30 | Engineer |\n", - "| Bob | 25 | Designer |\n", - "| Carol | 35 | Manager |\n\n", - "---\n\n", - "### Code Section\n", - "let x = 42\n", - "/= x * 2\n", - ); + let sample = "# "; let block_vec = blocks::parse_blocks(sample, "rust"); let (registry, layout) = Self::vec_to_registry(block_vec); Self { @@ -2235,24 +2250,21 @@ impl EditorState { None => return eval_interp, }; - // Find which module this block belongs to let my_module = self.modules.iter().find(|m| m.block_ids.contains(&block_id)); - // Evaluate and import root module exports (unless this IS the root) let is_root = my_module.map(|m| m.is_root).unwrap_or(false); if !is_root { if let Some(root) = self.modules.iter().find(|m| m.is_root) { - let root_text = self.module_source_text(root); - let mut root_interp = interp::Interpreter::new(); - crate::eval::evaluate_document_with_interp(&mut root_interp, &root_text); - eval_interp.import_all(&root_interp.exports()); + let mut visited: std::collections::HashSet = std::collections::HashSet::new(); + let root_exports = self.resolve_module_exports(root, &mut visited); + eval_interp.import_all(&root_exports); } } - // Find use declarations in all text blocks of this module and import those modules let use_block_ids: Vec = my_module .map(|m| m.block_ids.clone()) .unwrap_or_default(); + let my_module_name = my_module.map(|m| m.name.clone()).unwrap_or_default(); for &bid in &use_block_ids { if let Some(block) = self.registry.get(&bid) { if let Some(tb) = block.as_any().downcast_ref::() { @@ -2260,18 +2272,11 @@ impl EditorState { let use_decls = interp::extract_use_declarations(&text); for decl in &use_decls { if let Some(dep_module) = self.modules.iter().find(|m| m.name == decl.module) { - let dep_text = self.module_source_text(dep_module); - let mut dep_interp = interp::Interpreter::new(); - if let Some(root) = self.modules.iter().find(|m| m.is_root) { - if !dep_module.is_root { - let root_text = self.module_source_text(root); - let mut root_interp = interp::Interpreter::new(); - crate::eval::evaluate_document_with_interp(&mut root_interp, &root_text); - dep_interp.import_all(&root_interp.exports()); - } + let mut visited: std::collections::HashSet = std::collections::HashSet::new(); + if !my_module_name.is_empty() { + visited.insert(my_module_name.clone()); } - crate::eval::evaluate_document_with_interp(&mut dep_interp, &dep_text); - let dep_exports = dep_interp.exports(); + let dep_exports = self.resolve_module_exports(dep_module, &mut visited); match &decl.item { None => eval_interp.import_all(&dep_exports), Some(s) if s == "*" => eval_interp.import_all(&dep_exports), @@ -2286,6 +2291,46 @@ impl EditorState { eval_interp } + /// Recursively evaluate a module with its `use` declarations resolved. + fn resolve_module_exports( + &self, + module: &crate::module::Module, + visited: &mut std::collections::HashSet, + ) -> acord_core::interp::ModuleExports { + use acord_core::interp; + + if !module.name.is_empty() && !visited.insert(module.name.clone()) { + return interp::ModuleExports::default(); + } + + let mut interp = interp::Interpreter::new(); + + if !module.is_root { + if let Some(root) = self.modules.iter().find(|m| m.is_root) { + if root.name != module.name { + let root_exports = self.resolve_module_exports(root, visited); + interp.import_all(&root_exports); + } + } + } + + let module_text = self.module_source_text(module); + let use_decls = interp::extract_use_declarations(&module_text); + for decl in &use_decls { + if let Some(dep) = self.modules.iter().find(|m| m.name == decl.module) { + let dep_exports = self.resolve_module_exports(dep, visited); + match &decl.item { + None => interp.import_all(&dep_exports), + Some(s) if s == "*" => interp.import_all(&dep_exports), + Some(item) => { interp.import_item(&dep_exports, item); } + } + } + } + + crate::eval::evaluate_document_with_interp(&mut interp, &module_text); + interp.exports() + } + fn run_eval(&mut self) { self.rebuild_modules(); @@ -4499,12 +4544,24 @@ fn macos_key_binding(key_press: KeyPress) -> Option> { Some(Binding::Custom(Message::ZoomOut)) } // Cmd+0 lives in handle.rs now (FixUp); Cmd+Shift+0 resets zoom. - keyboard::Key::Character("[") if !modifiers.logo() && !modifiers.alt() && !modifiers.control() => { + keyboard::Key::Character("[") if !modifiers.logo() && !modifiers.alt() && !modifiers.control() && auto_pair::enabled(auto_pair::BRACKET) => { Some(Binding::Custom(Message::AutoPair("[", "]"))) } - keyboard::Key::Character("{") if !modifiers.logo() && !modifiers.alt() && !modifiers.control() => { + keyboard::Key::Character("{") if !modifiers.logo() && !modifiers.alt() && !modifiers.control() && auto_pair::enabled(auto_pair::BRACE) => { Some(Binding::Custom(Message::AutoPair("{", "}"))) } + keyboard::Key::Character("(") if !modifiers.logo() && !modifiers.alt() && !modifiers.control() && auto_pair::enabled(auto_pair::PAREN) => { + Some(Binding::Custom(Message::AutoPair("(", ")"))) + } + keyboard::Key::Character("'") if !modifiers.logo() && !modifiers.alt() && !modifiers.control() && auto_pair::enabled(auto_pair::SINGLE) => { + Some(Binding::Custom(Message::AutoPair("'", "'"))) + } + keyboard::Key::Character("\"") if !modifiers.logo() && !modifiers.alt() && !modifiers.control() && auto_pair::enabled(auto_pair::DOUBLE) => { + Some(Binding::Custom(Message::AutoPair("\"", "\""))) + } + keyboard::Key::Character("`") if !modifiers.logo() && !modifiers.alt() && !modifiers.control() && auto_pair::enabled(auto_pair::BACKTICK) => { + Some(Binding::Custom(Message::AutoPair("`", "`"))) + } keyboard::Key::Named(key::Named::Backspace) if modifiers.alt() => { Some(Binding::Sequence(vec![ Binding::Select(Motion::WordLeft), @@ -4612,24 +4669,14 @@ fn count_leading_char(s: &str, c: char) -> usize { /// `text`. column is interpreted as char count (cosmic-text convention). fn byte_offset_for_cursor(text: &str, pos: &text_widget::Position) -> usize { let mut byte = 0usize; - let mut line_idx = 0usize; - for line in text.split_inclusive('\n') { + for (line_idx, line) in text.split_inclusive('\n').enumerate() { if line_idx == pos.line { - let col = pos.column; - for (i, _) in line.char_indices().take(col) { - byte += line.as_bytes()[i..i + 1].len(); + for (col_idx, (ci, _)) in line.char_indices().enumerate() { + if col_idx == pos.column { return byte + ci; } } - // Walk col chars precisely. - let mut walked = 0usize; - for (ci, _) in line.char_indices() { - if walked == col { return byte.saturating_sub(line.len()) + ci; } - walked += 1; - } - // col >= line length: clamp to end of line content (before \n). return byte + line.trim_end_matches('\n').len(); } byte += line.len(); - line_idx += 1; } text.len() } diff --git a/viewport/src/lib.rs b/viewport/src/lib.rs index 6cd789d..9ddb09f 100644 --- a/viewport/src/lib.rs +++ b/viewport/src/lib.rs @@ -287,6 +287,19 @@ pub extern "C" fn viewport_set_gutter_rainbow(handle: *mut ViewportHandle, enabl } } +#[unsafe(no_mangle)] +pub extern "C" fn viewport_set_auto_pair_flags(handle: *mut ViewportHandle, flags: u32) { + editor::auto_pair::set_flags(flags as u8); + if let Some(h) = unsafe { handle.as_mut() } { + h.needs_redraw = true; + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn viewport_get_auto_pair_flags() -> u32 { + editor::auto_pair::flags() as u32 +} + #[unsafe(no_mangle)] pub extern "C" fn viewport_send_command(handle: *mut ViewportHandle, command: u32) { let h = match unsafe { handle.as_mut() } { diff --git a/windows/src/app.rs b/windows/src/app.rs index 0de2c29..3eff15f 100644 --- a/windows/src/app.rs +++ b/windows/src/app.rs @@ -15,6 +15,7 @@ use acord_viewport::{ viewport_create, viewport_destroy, viewport_render, viewport_resize, viewport_set_text, viewport_get_text, viewport_set_theme, viewport_set_lang, viewport_set_line_indicator, viewport_set_gutter_rainbow, + viewport_set_auto_pair_flags, viewport_send_command, viewport_free_string, ViewportHandle, }; @@ -64,6 +65,7 @@ impl App { let ind = CString::new(self.config.line_indicator()).unwrap(); viewport_set_line_indicator(self.handle, ind.as_ptr()); viewport_set_gutter_rainbow(self.handle, self.config.gutter_rainbow()); + viewport_set_auto_pair_flags(self.handle, self.config.auto_pair_flags()); } fn dispatch_menu(&mut self, action: MenuAction, event_loop: &ActiveEventLoop) { @@ -96,6 +98,14 @@ impl App { MenuAction::Undo => { /* TODO */ }, MenuAction::Redo => { /* TODO */ }, MenuAction::ExportCrate => { /* TODO */ }, + MenuAction::ToggleAutoPair(bit) => { + let new_flags = self.config.auto_pair_flags() ^ bit; + self.config.set_auto_pair_flags(new_flags); + viewport_set_auto_pair_flags(self.handle, new_flags); + if let Some(menu) = &self._menu { + menu.set_auto_pair_check(bit, (new_flags & bit) != 0); + } + } } } @@ -153,8 +163,8 @@ impl App { } fn new_note(&mut self) { - let empty = CString::new("").unwrap(); - viewport_set_text(self.handle, empty.as_ptr()); + let stub = CString::new("# ").unwrap(); + viewport_set_text(self.handle, stub.as_ptr()); if let Some(w) = &self.window { w.set_title("Acord"); } @@ -237,12 +247,15 @@ impl ApplicationHandler for App { self.handle = viewport_create(hwnd, w, h, self.scale); self.sync_settings(); - // Set up native menu bar. - let app_menu = AppMenu::new(); + let app_menu = AppMenu::new(self.config.auto_pair_flags()); #[cfg(target_os = "windows")] { if let raw_window_handle::RawWindowHandle::Win32(h) = raw { - unsafe { app_menu.menu.init_for_hwnd(h.hwnd.get()).ok(); } + let theme = match self.config.theme_mode() { + "light" => muda::MenuTheme::Light, + _ => muda::MenuTheme::Dark, + }; + unsafe { app_menu.menu.init_for_hwnd_with_theme(h.hwnd.get(), theme).ok(); } } } self._menu = Some(app_menu); diff --git a/windows/src/config.rs b/windows/src/config.rs index bcde3e2..223a201 100644 --- a/windows/src/config.rs +++ b/windows/src/config.rs @@ -56,6 +56,17 @@ impl Config { .map(PathBuf::from) .unwrap_or_else(|| config_dir().join("notes")) } + + pub fn auto_pair_flags(&self) -> u32 { + self.data.get("autoPairFlags") + .and_then(|s| s.parse().ok()) + .unwrap_or(63) + } + + pub fn set_auto_pair_flags(&mut self, flags: u32) { + self.data.insert("autoPairFlags".to_string(), flags.to_string()); + self.save(); + } } fn config_dir() -> PathBuf { diff --git a/windows/src/menu.rs b/windows/src/menu.rs index 89e691b..8c1961f 100644 --- a/windows/src/menu.rs +++ b/windows/src/menu.rs @@ -1,9 +1,17 @@ -use muda::{Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu, accelerator::Accelerator}; +use muda::{CheckMenuItem, Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu, accelerator::Accelerator}; use muda::accelerator::{Code, Modifiers}; +pub const AP_PAREN: u32 = 1; +pub const AP_BRACKET: u32 = 2; +pub const AP_BRACE: u32 = 4; +pub const AP_SINGLE: u32 = 8; +pub const AP_DOUBLE: u32 = 16; +pub const AP_BACKTICK: u32 = 32; + pub struct AppMenu { #[allow(dead_code)] pub menu: Menu, + auto_pair_items: Vec<(u32, CheckMenuItem)>, } pub enum MenuAction { @@ -27,10 +35,11 @@ pub enum MenuAction { Find, Settings, ExportCrate, + ToggleAutoPair(u32), } impl AppMenu { - pub fn new() -> Self { + pub fn new(auto_pair_flags: u32) -> Self { let menu = Menu::new(); let file = Submenu::new("File", true); @@ -61,12 +70,30 @@ impl AppMenu { edit.append(&MenuItem::with_id("italic", "Italic", true, Some(Accelerator::new(Some(Modifiers::CONTROL), Code::KeyI)))).ok(); edit.append(&MenuItem::with_id("table", "Insert Table", true, Some(Accelerator::new(Some(Modifiers::CONTROL), Code::KeyT)))).ok(); + edit.append(&PredefinedMenuItem::separator()).ok(); + let auto_pair_sub = Submenu::new("Auto Pair", true); + let pair_specs: [(u32, &str, &str); 6] = [ + (AP_PAREN, "ap_paren", "Parens ( )"), + (AP_BRACKET, "ap_bracket", "Brackets [ ]"), + (AP_BRACE, "ap_brace", "Braces { }"), + (AP_SINGLE, "ap_single", "Single quotes ' '"), + (AP_DOUBLE, "ap_double", "Double quotes \" \""), + (AP_BACKTICK, "ap_backtick", "Backticks ` `"), + ]; + let mut auto_pair_items: Vec<(u32, CheckMenuItem)> = Vec::with_capacity(6); + for (bit, id, label) in pair_specs { + let item = CheckMenuItem::with_id(id, label, true, (auto_pair_flags & bit) != 0, None); + auto_pair_sub.append(&item).ok(); + auto_pair_items.push((bit, item)); + } + edit.append(&auto_pair_sub).ok(); + let render = Submenu::new("Render", true); render.append(&MenuItem::with_id("live", "Live", true, None)).ok(); render.append(&MenuItem::with_id("editor", "Editor", true, None)).ok(); render.append(&MenuItem::with_id("view", "View", true, None)).ok(); render.append(&PredefinedMenuItem::separator()).ok(); - render.append(&MenuItem::with_id("eval", "Evaluate", true, Some(Accelerator::new(Some(Modifiers::CONTROL), Code::Enter)))).ok(); + render.append(&MenuItem::with_id("eval", "Evaluate", true, Some(Accelerator::new(Some(Modifiers::CONTROL), Code::KeyE)))).ok(); let view = Submenu::new("View", true); view.append(&MenuItem::with_id("zoom_in", "Zoom In", true, Some(Accelerator::new(Some(Modifiers::CONTROL), Code::Equal)))).ok(); @@ -78,7 +105,13 @@ impl AppMenu { menu.append(&render).ok(); menu.append(&view).ok(); - Self { menu } + Self { menu, auto_pair_items } + } + + pub fn set_auto_pair_check(&self, bit: u32, checked: bool) { + for (b, item) in &self.auto_pair_items { + if *b == bit { item.set_checked(checked); } + } } pub fn poll() -> Option { @@ -104,6 +137,12 @@ impl AppMenu { "find" => Some(MenuAction::Find), "settings" => Some(MenuAction::Settings), "export_crate" => Some(MenuAction::ExportCrate), + "ap_paren" => Some(MenuAction::ToggleAutoPair(AP_PAREN)), + "ap_bracket" => Some(MenuAction::ToggleAutoPair(AP_BRACKET)), + "ap_brace" => Some(MenuAction::ToggleAutoPair(AP_BRACE)), + "ap_single" => Some(MenuAction::ToggleAutoPair(AP_SINGLE)), + "ap_double" => Some(MenuAction::ToggleAutoPair(AP_DOUBLE)), + "ap_backtick" => Some(MenuAction::ToggleAutoPair(AP_BACKTICK)), _ => None, } })