diff --git a/Cargo.lock b/Cargo.lock index 5eebe7e..751dd12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "aligned" version = "0.4.3" @@ -345,6 +354,21 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -792,6 +816,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -970,6 +1003,17 @@ dependencies = [ "zune-inflate", ] +[[package]] +name = "fancy-regex" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f" +dependencies = [ + "bit-set", + "regex-automata", + "regex-syntax", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -1021,6 +1065,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" @@ -1205,6 +1255,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "getopts" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" +dependencies = [ + "unicode-width", +] + [[package]] name = "getrandom" version = "0.3.4" @@ -1396,6 +1455,7 @@ dependencies = [ "iced_core", "iced_debug", "iced_futures", + "iced_highlighter", "iced_renderer", "iced_runtime", "iced_widget", @@ -1468,6 +1528,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "iced_highlighter" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb24e1610502779b4e307c6f95da616ec7e883b67a18108cb0a036b169e50359" +dependencies = [ + "iced_core", + "two-face", +] + [[package]] name = "iced_program" version = "0.14.0" @@ -1547,9 +1617,11 @@ version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1596afa0d3109c2618e8bc12bae6c11d3064df8f95c42dfce570397dbe957ab" dependencies = [ + "iced_highlighter", "iced_renderer", "log", "num-traits", + "pulldown-cmark", "rustc-hash 2.1.1", "thiserror 2.0.17", "unicode-segmentation", @@ -1644,6 +1716,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + [[package]] name = "jni" version = "0.21.1" @@ -1786,6 +1864,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -2043,6 +2127,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-derive" version = "0.4.2" @@ -2615,6 +2705,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plist" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +dependencies = [ + "base64", + "indexmap", + "quick-xml", + "serde", + "time", +] + [[package]] name = "png" version = "0.18.0" @@ -2663,6 +2766,12 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2715,6 +2824,25 @@ dependencies = [ "syn", ] +[[package]] +name = "pulldown-cmark" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86ba2052aebccc42cbbb3ed234b8b13ce76f75c3551a303cb2bcffcff12bb14" +dependencies = [ + "bitflags 2.10.0", + "getopts", + "memchr", + "pulldown-cmark-escape", + "unicase", +] + +[[package]] +name = "pulldown-cmark-escape" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" + [[package]] name = "pxfm" version = "0.1.27" @@ -2918,6 +3046,23 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + [[package]] name = "renderdoc-sys" version = "1.1.0" @@ -3077,6 +3222,19 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + [[package]] name = "serde_repr" version = "0.1.20" @@ -3308,6 +3466,27 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syntect" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925" +dependencies = [ + "bincode", + "fancy-regex", + "flate2", + "fnv", + "once_cell", + "plist", + "regex-syntax", + "serde", + "serde_derive", + "serde_json", + "thiserror 2.0.17", + "walkdir", + "yaml-rust", +] + [[package]] name = "sys-locale" version = "0.3.2" @@ -3393,6 +3572,37 @@ dependencies = [ "zune-jpeg 0.4.21", ] +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tiny-skia" version = "0.11.4" @@ -3517,6 +3727,17 @@ dependencies = [ "core_maths", ] +[[package]] +name = "two-face" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e51b6e60e545cfdae5a4639ff423818f52372211a8d9a3e892b4b0761f76b2" +dependencies = [ + "serde", + "serde_derive", + "syntect", +] + [[package]] name = "uds_windows" version = "1.1.0" @@ -3528,6 +3749,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + [[package]] name = "unicode-bidi" version = "0.3.18" @@ -4508,6 +4735,15 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "yazi" version = "0.2.1" @@ -4601,6 +4837,12 @@ dependencies = [ "syn", ] +[[package]] +name = "zmij" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" + [[package]] name = "zoeae" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 500c16d..8daeef9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,6 @@ version = "0.1.0" edition = "2024" [dependencies] -iced = { version = "0.14.0", features = ["image"] } +iced = { version = "0.14.0", features = ["image", "markdown", "highlighter"] } rfd = "0.17.1" uuid = { version = "1.19.0", features = ["v4"] } diff --git a/src/main.rs b/src/main.rs index 86dae95..ec0a634 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,9 +4,11 @@ use std::path::PathBuf; use iced::padding::bottom; use iced::widget::text_editor::{Binding, KeyPress}; -use iced::widget::{button, column, container, pick_list, row, scrollable, text, text_editor}; +use iced::widget::{ + button, column, container, markdown, pick_list, row, scrollable, text, text_editor, +}; use iced::window::icon; -use iced::{Background, Border, Element, border}; +use iced::{Background, Border, Element, Task, border}; use iced::{Font, Length, Theme}; use iced::{keyboard, window}; use rfd::FileDialog; @@ -36,10 +38,10 @@ const MIN_EDITOR_FONT_SIZE: u32 = 12; // TODO: Handle creating new files // TODO: Opening another window - struct File { content: text_editor::Content, path: Option, + markdown: Vec, } impl Default for File { @@ -47,18 +49,21 @@ impl Default for File { File { content: text_editor::Content::new(), path: None, + markdown: Vec::new(), } } } -// TODO: Chat says I need to update -// the files map to be a map between -// an id and an index -// Then files need to be held in; -// list +#[derive(Default)] +enum Mode { + #[default] + Edit, + Preview, +} #[derive(Default)] struct State { + mode: Mode, files: Vec, current_file: usize, editor_font_size: u32, @@ -108,6 +113,7 @@ enum ViewAction { Increase, Decrease, Reset, + TogglePreview, } impl ViewAction { @@ -115,6 +121,7 @@ impl ViewAction { ViewAction::Increase, ViewAction::Decrease, ViewAction::Reset, + ViewAction::TogglePreview, ]; } @@ -124,6 +131,7 @@ impl std::fmt::Display for ViewAction { ViewAction::Decrease => write!(f, "Decrease font"), ViewAction::Increase => write!(f, "Increase font"), ViewAction::Reset => write!(f, "Reset font"), + ViewAction::TogglePreview => write!(f, "Preview"), } } } @@ -134,8 +142,14 @@ enum Message { FileActionSelected(FileAction), ViewActionSelected(ViewAction), SwitchTab(usize), + LinkClicked(String), } +// TODO: Can we at least make +// theme configurable +// and then maybe even define +// complete custom and/or use +// existing as starter fn theme(_state: &State) -> Theme { Theme::Dark } @@ -212,6 +226,7 @@ fn view(state: &State) -> Element<'_, Message> { let n = keyboard::Key::Character("n".into()); let s = keyboard::Key::Character("s".into()); let o = keyboard::Key::Character("o".into()); + let w = keyboard::Key::Character("w".into()); let minus = keyboard::Key::Character("-".into()); let equals = keyboard::Key::Character("=".into()); let zero = keyboard::Key::Character("0".into()); @@ -221,11 +236,18 @@ fn view(state: &State) -> Element<'_, Message> { let is_save_as = key_press.modifiers.command() && key_press.modifiers.shift() && key_press.key == s; let is_open = key_press.modifiers.command() && key_press.key == o; + let is_close = key_press.modifiers.command() && key_press.key == w; let is_increase_font = key_press.modifiers.command() && key_press.key == equals; let is_decrease_font = key_press.modifiers.command() && key_press.key == minus; let is_reset_font = key_press.modifiers.command() && key_press.key == zero; + if is_close { + return Some(Binding::Custom(Message::FileActionSelected( + FileAction::Close(Some(state.current_file)), + ))); + } + if is_reset_font { return Some(Binding::Custom(Message::ViewActionSelected( ViewAction::Reset, @@ -271,6 +293,14 @@ fn view(state: &State) -> Element<'_, Message> { text_editor::Binding::from_key_press(key_press) }); + let mut markdown_styles: markdown::Style = Theme::Dark.into(); + markdown_styles.font = CUSTOM_FONT; + + let markdown_settings = markdown::Settings::with_text_size(state.editor_font_size, markdown_styles); + let markdown_preview = + markdown::view(¤t_file.markdown, markdown_settings).map(Message::LinkClicked); + + let preview_container = container(row![markdown_preview]).height(Length::Fill); let editor_container = container(row![editor]).height(Length::Fill); let cursor_position = current_file.content.cursor().position; @@ -290,7 +320,12 @@ fn view(state: &State) -> Element<'_, Message> { let status_bar = container(row![file_path_text, cursor_text].spacing(10)); - container(column![tabs, action_bar, editor_container, status_bar]) + let main = match state.mode { + Mode::Edit => editor_container, + Mode::Preview => preview_container, + }; + + container(column![tabs, action_bar, main, status_bar]) .padding(10) .into() } @@ -336,11 +371,13 @@ fn open_file() -> (Option, String) { } } -fn update(state: &mut State, message: Message) { +fn update(state: &mut State, message: Message) -> Task { match message { Message::Edit(action) => { + // TODO: Probably shouldn't parse every time we edited let current_file = &mut state.files[state.current_file]; current_file.content.perform(action); + current_file.markdown = markdown::parse(¤t_file.content.text()).collect(); } Message::FileActionSelected(action) => { state.selected_file_action = None; @@ -361,13 +398,14 @@ fn update(state: &mut State, message: Message) { { file.content = text_editor::Content::with_text(&content); state.current_file = file_index; - return; + return Task::none(); } } let opened_file = File { path, content: text_editor::Content::with_text(&content), + markdown: markdown::parse(&content).collect(), }; state.files.push(opened_file); @@ -391,6 +429,17 @@ fn update(state: &mut State, message: Message) { None => state.current_file, }; + if state.files.len() == 1 { + return iced::exit(); + } + + if state.files.len() - 1 == idx_to_close { + state.current_file = state.files.len() - 2; + state.files.remove(idx_to_close); + return Task::none(); + } + + state.current_file -= 1; state.files.remove(idx_to_close); } } @@ -401,25 +450,39 @@ fn update(state: &mut State, message: Message) { match action { ViewAction::Increase => { if state.editor_font_size >= MAX_EDITOR_FONT_SIZE { - return; + return Task::none(); } state.editor_font_size += 2; } ViewAction::Decrease => { if state.editor_font_size <= MIN_EDITOR_FONT_SIZE { - return; + return Task::none(); } state.editor_font_size -= 2; } - ViewAction::Reset => state.editor_font_size = DEFAULT_EDITOR_FONT_SIZE, + ViewAction::Reset => { + state.editor_font_size = DEFAULT_EDITOR_FONT_SIZE; + } + // TODO: Carry previous mode when toggling + // so that if we already in preview we can + // just put the user back where they were + ViewAction::TogglePreview => match state.mode { + Mode::Edit => state.mode = Mode::Preview, + Mode::Preview => state.mode = Mode::Edit, + }, } } Message::SwitchTab(file_id) => { state.current_file = file_id; } + Message::LinkClicked(link) => { + print!("Link clicked: {}", link); + } } + + Task::none() } fn boot() -> State {