Added more auto-pairs types, '', "";
Added toggles in the menu for which auto-pairs to complete.
This commit is contained in:
parent
bae246f08d
commit
596482436a
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -300,7 +300,7 @@ class AppState: ObservableObject {
|
|||
let id = bridge.newDocument()
|
||||
currentNoteID = id
|
||||
selectedNoteIDs = [id]
|
||||
documentText = ""
|
||||
documentText = "# "
|
||||
evalResults = [:]
|
||||
modified = false
|
||||
currentFileURL = nil
|
||||
|
|
|
|||
|
|
@ -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() }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -13,6 +13,20 @@
|
|||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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<String> = 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<crate::selection::BlockId> = 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::<TextBlock>() {
|
||||
|
|
@ -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<String> = 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<String>,
|
||||
) -> 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<Binding<Message>> {
|
|||
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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() } {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<MenuAction> {
|
||||
|
|
@ -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,
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue