From 20190ac784303457e2091af1b9306fa87b96a44c Mon Sep 17 00:00:00 2001 From: kjuulh Date: Sun, 30 Jun 2024 17:31:25 +0200 Subject: [PATCH] feat: add markdown editing mode Signed-off-by: kjuulh --- Cargo.lock | 243 ++++++++-------- Cargo.toml | 3 + crates/hyperlog-tui/Cargo.toml | 4 + crates/hyperlog-tui/src/app.rs | 45 ++- crates/hyperlog-tui/src/command_parser.rs | 2 + crates/hyperlog-tui/src/commands.rs | 1 + crates/hyperlog-tui/src/commands/archive.rs | 1 - crates/hyperlog-tui/src/commands/open_item.rs | 59 ++++ .../src/components/graph_explorer.rs | 13 +- crates/hyperlog-tui/src/editor.rs | 260 ++++++++++++++++++ crates/hyperlog-tui/src/lib.rs | 2 + crates/hyperlog-tui/src/models.rs | 3 + crates/hyperlog-tui/src/project_dirs.rs | 5 + 13 files changed, 519 insertions(+), 122 deletions(-) create mode 100644 crates/hyperlog-tui/src/commands/open_item.rs create mode 100644 crates/hyperlog-tui/src/editor.rs create mode 100644 crates/hyperlog-tui/src/project_dirs.rs diff --git a/Cargo.lock b/Cargo.lock index a2f7c6e..7cb4d98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,9 +77,9 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] @@ -119,7 +119,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -130,7 +130,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -161,7 +161,7 @@ dependencies = [ "futures-util", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.28", + "hyper 0.14.29", "itoa", "matchit", "memchr", @@ -250,9 +250,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.72" +version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ "addr2line", "cc", @@ -289,9 +289,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" dependencies = [ "serde", ] @@ -356,9 +356,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.98" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" +checksum = "2755ff20a1d93490d26ba33a6f092a38a508398a5320df5d4b3014fcccce9410" [[package]] name = "cfg-if" @@ -368,9 +368,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.4" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" dependencies = [ "clap_builder", "clap_derive", @@ -378,9 +378,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" dependencies = [ "anstream", "anstyle", @@ -390,21 +390,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "colorchoice" @@ -513,7 +513,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "crossterm_winapi", "futures-core", "libc", @@ -619,9 +619,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" dependencies = [ "serde", ] @@ -685,7 +685,7 @@ checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ "futures-core", "futures-sink", - "spin 0.9.8", + "spin", ] [[package]] @@ -770,7 +770,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -973,12 +973,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "futures-core", + "futures-util", "http 1.1.0", "http-body 1.0.0", "pin-project-lite", @@ -986,9 +986,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" @@ -1014,9 +1014,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" dependencies = [ "bytes", "futures-channel", @@ -1061,7 +1061,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.28", + "hyper 0.14.29", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -1162,6 +1162,7 @@ dependencies = [ "directories", "dirs", "futures", + "hex", "human-panic", "hyperlog-core", "hyperlog-protos", @@ -1170,12 +1171,15 @@ dependencies = [ "ropey", "serde", "serde_json", + "sha2", "similar-asserts", "tempfile", "tokio", + "toml", "tonic", "tracing", "tracing-subscriber", + "uuid", ] [[package]] @@ -1240,11 +1244,11 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.5.2", + "spin", ] [[package]] @@ -1265,7 +1269,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "libc", ] @@ -1298,9 +1302,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lru" @@ -1338,9 +1342,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mime" @@ -1356,9 +1360,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] @@ -1466,9 +1470,9 @@ dependencies = [ [[package]] name = "object" -version = "0.35.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" +checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" dependencies = [ "memchr", ] @@ -1526,7 +1530,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.1", + "redox_syscall 0.5.2", "smallvec", "windows-targets 0.52.5", ] @@ -1579,7 +1583,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -1640,14 +1644,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] name = "proc-macro2" -version = "1.0.84" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -1679,7 +1683,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.66", + "syn 2.0.68", "tempfile", ] @@ -1693,7 +1697,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -1750,7 +1754,7 @@ version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "cassowary", "compact_str", "crossterm", @@ -1775,11 +1779,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", ] [[package]] @@ -1795,14 +1799,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] @@ -1816,13 +1820,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.3", + "regex-syntax 0.8.4", ] [[package]] @@ -1833,9 +1837,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "ring" @@ -1847,7 +1851,7 @@ dependencies = [ "cfg-if", "getrandom", "libc", - "spin 0.9.8", + "spin", "untrusted", "windows-sys 0.52.0", ] @@ -1894,7 +1898,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -2028,7 +2032,7 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -2062,14 +2066,14 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" dependencies = [ "itoa", "ryu", @@ -2223,12 +2227,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spin" version = "0.9.8" @@ -2250,11 +2248,10 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" +checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" dependencies = [ - "itertools 0.12.1", "nom", "unicode_categories", ] @@ -2363,7 +2360,7 @@ checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" dependencies = [ "atoi", "base64 0.21.7", - "bitflags 2.5.0", + "bitflags 2.6.0", "byteorder", "bytes", "crc", @@ -2407,7 +2404,7 @@ checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" dependencies = [ "atoi", "base64 0.21.7", - "bitflags 2.5.0", + "bitflags 2.6.0", "byteorder", "crc", "dotenvy", @@ -2471,7 +2468,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a" dependencies = [ "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -2505,31 +2502,31 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.26.2" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote", "rustversion", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -2544,9 +2541,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" dependencies = [ "proc-macro2", "quote", @@ -2594,7 +2591,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -2640,9 +2637,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" dependencies = [ "tinyvec_macros", ] @@ -2655,9 +2652,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", @@ -2684,13 +2681,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -2730,9 +2727,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.13" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" dependencies = [ "serde", "serde_spanned", @@ -2751,14 +2748,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.13" +version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" dependencies = [ "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", + "winnow", ] [[package]] @@ -2775,7 +2773,7 @@ dependencies = [ "h2", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.28", + "hyper 0.14.29", "hyper-timeout", "percent-encoding", "pin-project", @@ -2802,7 +2800,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -2831,7 +2829,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "bytes", "http 1.1.0", "http-body 1.0.0", @@ -2874,7 +2872,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] @@ -2973,9 +2971,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode_categories" @@ -2991,9 +2989,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -3008,15 +3006,15 @@ checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.8.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" dependencies = [ "getrandom", ] @@ -3237,6 +3235,15 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +[[package]] +name = "winnow" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +dependencies = [ + "memchr", +] + [[package]] name = "zerocopy" version = "0.7.34" @@ -3254,7 +3261,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.68", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 698ec9b..b2f3377 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,9 @@ itertools = "0.13.0" uuid = { version = "1.8.0", features = ["v4"] } tonic = { version = "0.11.0", features = ["tls", "tls-roots"] } futures = { version = "0.3.30" } +sha2 = { version = "0.10.8" } +hex = { version = "0.4.3" } +toml = { version = "0.8.14" } [workspace.package] version = "0.2.0" diff --git a/crates/hyperlog-tui/Cargo.toml b/crates/hyperlog-tui/Cargo.toml index c7f382b..9801066 100644 --- a/crates/hyperlog-tui/Cargo.toml +++ b/crates/hyperlog-tui/Cargo.toml @@ -17,6 +17,10 @@ serde_json.workspace = true itertools.workspace = true tonic.workspace = true futures.workspace = true +sha2.workspace = true +uuid.workspace = true +hex.workspace = true +toml.workspace = true ratatui = "0.26.2" crossterm = { version = "0.27.0", features = ["event-stream"] } diff --git a/crates/hyperlog-tui/src/app.rs b/crates/hyperlog-tui/src/app.rs index bb67710..f6556a6 100644 --- a/crates/hyperlog-tui/src/app.rs +++ b/crates/hyperlog-tui/src/app.rs @@ -1,4 +1,5 @@ use hyperlog_core::log::GraphItem; +use itertools::Itertools; use ratatui::{ prelude::*, widgets::{Block, Borders, Padding, Paragraph}, @@ -6,8 +7,9 @@ use ratatui::{ use crate::{ command_parser::CommandParser, - commands::{batch::BatchCommand, IntoCommand}, + commands::{batch::BatchCommand, update_item::UpdateItemCommandExt, Command, IntoCommand}, components::graph_explorer::GraphExplorer, + editor, models::IOEvent, state::SharedState, Msg, @@ -102,6 +104,11 @@ impl<'a> App<'a> { Msg::MoveUp => self.graph_explorer.move_up()?, Msg::OpenCreateItemDialog => self.open_dialog(), Msg::OpenCreateItemDialogBelow => self.open_dialog_below(), + Msg::OpenEditor { item } => { + if let Some(cmd) = self.open_editor(item) { + batch.with(cmd); + } + } Msg::OpenEditItemDialog { item } => self.open_edit_item_dialog(item), Msg::EnterInsertMode => self.mode = Mode::Insert, Msg::EnterViewMode => self.mode = Mode::View, @@ -212,6 +219,42 @@ impl<'a> App<'a> { self.mode = Mode::Insert; } } + + fn open_editor(&self, item: &GraphItem) -> Option { + match editor::EditorSession::new(item).execute() { + Ok(None) => { + tracing::info!("editor returned without changes, skipping"); + } + Ok(Some(item)) => { + if let GraphItem::Item { + title, + description, + state, + } = item + { + return Some( + self.state.update_item_command().command( + &self.root, + &self + .graph_explorer + .get_current_path() + .iter() + .map(|s| s.as_str()) + .collect_vec(), + &title, + &description, + state, + ), + ); + } + } + Err(e) => { + tracing::error!("failed to run editor with: {}", e); + } + } + + None + } } impl<'a> Widget for &mut App<'a> { diff --git a/crates/hyperlog-tui/src/command_parser.rs b/crates/hyperlog-tui/src/command_parser.rs index 9c1d2a5..a508576 100644 --- a/crates/hyperlog-tui/src/command_parser.rs +++ b/crates/hyperlog-tui/src/command_parser.rs @@ -9,6 +9,7 @@ pub enum Commands { CreateItem { name: String }, CreateBelow { name: String }, Edit, + Open, ShowAll, HideDone, @@ -52,6 +53,7 @@ impl CommandParser { "show-all" => Some(Commands::ShowAll), "hide-done" => Some(Commands::HideDone), "test" => Some(Commands::Test), + "o" | "open" => Some(Commands::Open), _ => None, }, None => None, diff --git a/crates/hyperlog-tui/src/commands.rs b/crates/hyperlog-tui/src/commands.rs index bee3c9b..0880dc5 100644 --- a/crates/hyperlog-tui/src/commands.rs +++ b/crates/hyperlog-tui/src/commands.rs @@ -5,6 +5,7 @@ pub mod batch; pub mod archive; pub mod create_item; pub mod create_section; +pub mod open_item; pub mod open_update_item_dialog; pub mod toggle_item; pub mod update_graph; diff --git a/crates/hyperlog-tui/src/commands/archive.rs b/crates/hyperlog-tui/src/commands/archive.rs index 8cc61ae..acbd918 100644 --- a/crates/hyperlog-tui/src/commands/archive.rs +++ b/crates/hyperlog-tui/src/commands/archive.rs @@ -1,4 +1,3 @@ -use hyperlog_core::log::ItemState; use itertools::Itertools; use crate::{ diff --git a/crates/hyperlog-tui/src/commands/open_item.rs b/crates/hyperlog-tui/src/commands/open_item.rs new file mode 100644 index 0000000..aa71d7a --- /dev/null +++ b/crates/hyperlog-tui/src/commands/open_item.rs @@ -0,0 +1,59 @@ +use crate::{ + models::{IOEvent, Msg}, + querier::Querier, + state::SharedState, +}; + +pub struct OpenItemCommand { + querier: Querier, +} + +impl OpenItemCommand { + pub fn new(querier: Querier) -> Self { + Self { querier } + } + + pub fn command(self, root: &str, path: Vec) -> super::Command { + let root = root.to_string(); + + super::Command::new(|dispatch| { + tokio::spawn(async move { + dispatch.send(Msg::OpenItem(IOEvent::Initialized)); + + let item = match self.querier.get_async(&root, path).await { + Ok(item) => match item { + Some(item) => { + dispatch.send(Msg::OpenItem(IOEvent::Success(()))); + item + } + None => { + dispatch.send(Msg::OpenItem(IOEvent::Failure( + "failed to find a valid item for path".into(), + ))); + + return; + } + }, + Err(e) => { + dispatch.send(Msg::OpenItem(IOEvent::Failure(e.to_string()))); + + return; + } + }; + + dispatch.send(Msg::OpenEditor { item }); + }); + None + }) + } +} + +pub trait OpenItemCommandExt { + fn open_item_command(&self) -> OpenItemCommand; +} + +impl OpenItemCommandExt for SharedState { + fn open_item_command(&self) -> OpenItemCommand { + OpenItemCommand::new(self.querier.clone()) + } +} diff --git a/crates/hyperlog-tui/src/components/graph_explorer.rs b/crates/hyperlog-tui/src/components/graph_explorer.rs index 4edec0f..d8da75a 100644 --- a/crates/hyperlog-tui/src/components/graph_explorer.rs +++ b/crates/hyperlog-tui/src/components/graph_explorer.rs @@ -7,7 +7,7 @@ use crate::{ command_parser::Commands, commands::{ archive::ArchiveCommandExt, batch::BatchCommand, create_item::CreateItemCommandExt, - create_section::CreateSectionCommandExt, + create_section::CreateSectionCommandExt, open_item::OpenItemCommandExt, open_update_item_dialog::OpenUpdateItemDialogCommandExt, toggle_item::ToggleItemCommandExt, update_graph::UpdateGraphCommandExt, Command, IntoCommand, }, @@ -144,7 +144,7 @@ impl<'a> GraphExplorer<'a> { /// Choses: 0.1.0.0 else nothing pub(crate) fn move_right(&mut self) -> Result<()> { if let Some(graph) = self.linearize_graph() { - tracing::debug!("graph: {:?}", graph); + tracing::trace!("graph: {:?}", graph); let position_items = &self.inner.current_position; if let Some(next_item) = graph.next_right(position_items) { @@ -338,6 +338,15 @@ impl<'a> GraphExplorer<'a> { None }))); } + Commands::Open => { + if self.get_current_item().is_some() { + batch.with( + self.state + .open_item_command() + .command(&self.inner.root, self.get_current_path()), + ); + } + } _ => (), } diff --git a/crates/hyperlog-tui/src/editor.rs b/crates/hyperlog-tui/src/editor.rs new file mode 100644 index 0000000..b96b420 --- /dev/null +++ b/crates/hyperlog-tui/src/editor.rs @@ -0,0 +1,260 @@ +use std::{ + io::{Read, Write}, + path::{Path, PathBuf}, + time::SystemTime, +}; + +use anyhow::{anyhow, Context}; +use hyperlog_core::log::{GraphItem, ItemState}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use sha2::Digest; + +use crate::project_dirs::get_project_dir; + +pub struct EditorSession<'a> { + item: &'a GraphItem, +} + +struct EditorFile { + title: String, + metadata: Metadata, + body: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +struct Metadata { + state: ItemState, +} + +impl EditorFile { + pub fn serialize(&self) -> anyhow::Result { + let metadata = + toml::to_string_pretty(&self.metadata).context("failed to serialize metadata")?; + + let frontmatter = format!("+++\n{}+++\n", metadata); + + Ok(format!( + "{}\n# {}\n\n{}", + frontmatter, self.title, self.body + )) + } +} + +impl TryFrom<&GraphItem> for EditorFile { + type Error = anyhow::Error; + + fn try_from(value: &GraphItem) -> Result { + if let GraphItem::Item { + title, + description, + state, + } = value.clone() + { + Ok(Self { + title, + metadata: Metadata { state }, + body: description, + }) + } else { + anyhow::bail!("can only generate a file based on items") + } + } +} + +impl TryFrom<&str> for EditorFile { + type Error = anyhow::Error; + + fn try_from(value: &str) -> Result { + let value = value.to_string(); + + let frontmatter_parts = value.split("+++").filter(|p| !p.is_empty()).collect_vec(); + let frontmatter_content = frontmatter_parts + .first() + .ok_or(anyhow::anyhow!("no front matter parts were found"))?; + + tracing::trace!("parsing frontmatter content: {}", frontmatter_content); + let metadata: Metadata = toml::from_str(frontmatter_content)?; + + let line_parts = value.split("\n"); + + let title = line_parts + .clone() + .find(|p| p.starts_with("# ")) + .map(|t| t.trim_start_matches("# ")) + .ok_or(anyhow!("an editor file requires a title with heading 1"))?; + let body = line_parts + .skip_while(|p| !p.starts_with("# ")) + .skip(1) + .skip_while(|p| p.is_empty()) + .collect_vec() + .join("\n"); + + Ok(Self { + title: title.to_string(), + metadata, + body, + }) + } +} + +impl From for GraphItem { + fn from(value: EditorFile) -> Self { + Self::Item { + title: value.title, + description: value.body, + state: value.metadata.state, + } + } +} + +struct SessionFile { + path: PathBuf, + loaded: SystemTime, +} + +impl SessionFile { + pub fn get_path(&self) -> &Path { + self.path.as_path() + } + + pub fn is_changed(&self) -> anyhow::Result { + let modified = self.path.metadata()?.modified()?; + + Ok(self.loaded < modified) + } +} + +impl Drop for SessionFile { + fn drop(&mut self) { + if self.path.exists() { + tracing::debug!("cleaning up file: {}", self.path.display()); + + if let Err(e) = std::fs::remove_file(&self.path) { + tracing::error!( + "failed to cleanup file: {}, error: {}", + self.path.display(), + e + ); + } + } + } +} + +impl<'a> EditorSession<'a> { + pub fn new(item: &'a GraphItem) -> Self { + Self { item } + } + + fn get_file_path(&mut self) -> anyhow::Result { + let name = self + .item + .get_digest() + .ok_or(anyhow::anyhow!("item doesn't have a title"))?; + + let file_path = get_project_dir() + .data_dir() + .join("edit") + .join(format!("{name}.md")); + + Ok(file_path) + } + + fn prepare_file(&mut self) -> anyhow::Result { + let file_path = self.get_file_path()?; + + if let Some(parent) = file_path.parent() { + tracing::debug!("creating parent dir: {}", parent.display()); + std::fs::create_dir_all(parent).context("failed to create dir for edit file")?; + } + + let mut file = + std::fs::File::create(&file_path).context("failed to create file for edit file")?; + + tracing::debug!("writing contents to file: {}", file_path.display()); + let editor_file = EditorFile::try_from(self.item)?; + file.write_all( + editor_file + .serialize() + .context("failed to serialize item to file")? + .as_bytes(), + ) + .context("failed to write to file")?; + + let modified_time = file.metadata()?.modified()?; + + Ok(SessionFile { + path: file_path, + loaded: modified_time, + }) + } + + fn get_item_from_file(&self, session_file: SessionFile) -> anyhow::Result { + let mut file = std::fs::File::open(&session_file.path)?; + + let mut content = String::new(); + file.read_to_string(&mut content)?; + + let editor_file = EditorFile::try_from(content.as_str())?; + + Ok(editor_file.into()) + } + + pub fn execute(&mut self) -> anyhow::Result> { + let editor = std::env::var("EDITOR").context("no editor was found for EDITOR env var")?; + let session_file = self.prepare_file()?; + + tracing::debug!( + "opening editor: {} at path: {}", + editor, + session_file.get_path().display() + ); + if let Err(e) = std::process::Command::new(editor) + .arg(session_file.get_path()) + .status() + { + tracing::error!("failed command with: {}", e); + return Ok(None); + } + + tracing::debug!( + "returning from editor, checking file: {}", + session_file.get_path().display() + ); + if session_file.is_changed()? { + tracing::debug!( + "file: {} changed, updating item", + session_file.get_path().display() + ); + + Ok(Some(self.get_item_from_file(session_file)?)) + } else { + Ok(None) + } + } +} + +trait ItemExt { + fn get_digest(&self) -> Option; +} + +impl<'a> ItemExt for &'a GraphItem { + fn get_digest(&self) -> Option { + if let GraphItem::Item { title, .. } = self { + let digest = sha2::Sha256::digest(title.as_bytes()); + let digest_hex = hex::encode(digest); + + Some(format!( + "{}_{}", + title + .chars() + .filter(|c| c.is_ascii_alphanumeric()) + .take(10) + .collect::(), + digest_hex.chars().take(10).collect::() + )) + } else { + None + } + } +} diff --git a/crates/hyperlog-tui/src/lib.rs b/crates/hyperlog-tui/src/lib.rs index d0d9dd9..2717cbb 100644 --- a/crates/hyperlog-tui/src/lib.rs +++ b/crates/hyperlog-tui/src/lib.rs @@ -33,7 +33,9 @@ mod events; mod querier; mod storage; +mod editor; mod logging; +mod project_dirs; mod terminal; pub async fn execute(state: State) -> Result<()> { diff --git a/crates/hyperlog-tui/src/models.rs b/crates/hyperlog-tui/src/models.rs index 740a2cf..0b3e033 100644 --- a/crates/hyperlog-tui/src/models.rs +++ b/crates/hyperlog-tui/src/models.rs @@ -12,6 +12,7 @@ pub enum Msg { OpenCreateItemDialog, OpenCreateItemDialogBelow, OpenEditItemDialog { item: GraphItem }, + OpenEditor { item: GraphItem }, Interact, EnterInsertMode, @@ -30,6 +31,8 @@ pub enum Msg { Archive(IOEvent<()>), OpenUpdateItemDialog(IOEvent<()>), + + OpenItem(IOEvent<()>), } #[derive(Debug)] diff --git a/crates/hyperlog-tui/src/project_dirs.rs b/crates/hyperlog-tui/src/project_dirs.rs new file mode 100644 index 0000000..de85f38 --- /dev/null +++ b/crates/hyperlog-tui/src/project_dirs.rs @@ -0,0 +1,5 @@ +use directories::ProjectDirs; + +pub fn get_project_dir() -> ProjectDirs { + ProjectDirs::from("io", "kjuulh", "hyperlog").expect("to be able to get project dirs") +}