From c1e41a872096708086e0d3bacc89f7b9ed4d62da Mon Sep 17 00:00:00 2001 From: Connor Keane Date: Sun, 5 May 2024 15:16:06 -0600 Subject: [PATCH 1/7] feat: start daily note commands --- src/commands.rs | 58 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/commands.rs diff --git a/src/commands.rs b/src/commands.rs new file mode 100644 index 00000000..66b6da0c --- /dev/null +++ b/src/commands.rs @@ -0,0 +1,58 @@ +use chrono::{NaiveDateTime, TimeDelta}; +use chrono::format::{Parsed, parse, strftime::StrftimeItems}; + +enum JournalIncrement { + Hours(i8), + Days(i8), + Weeks(i8), + Months(i8), + Years(i8) +} + + +fn file_to_datetime(filename: &str, format: &str) -> Result { + // default to the beginning of an underspecified interval, + // top of hour, beginning of day, start of week + + let items = StrftimeItems::new(format); + let mut parsed = Parsed::new(); + parse(&mut parsed, "2024-04-01", items); + parsed.to_naive_datetime_with_offset(0) +} + + +fn datetime_to_file(datetime: NaiveDateTime, format: &str) -> String { + datetime.format(format).to_string() +} + +fn increment_file(filename: &str, increment: JournalIncrement, format: &str) -> Result { + let current_as_datetime = file_to_datetime(filename, format); + + let next_as_datetime = match increment { + JournalIncrement::Hours(hours) => current_as_datetime.checked_add_signed(TimeDelta::hours(hours)), + JournalIncrement::Days(days) => current_as_datetime.checked_add_signed(TimeDelta::days(days)), + JournalIncrement::Weeks(weeks) => current_as_datetime.checked_add_signed(TimeDelta::weeks(weeks)), + JournalIncrement::Months(months) => current_as_datetime.checked_add_months(months), + JournalIncrement::Years(years) => current_as_datetime.checked_add_months(12*years), + } + + datetime_to_file(next_as_datetime, format) +} + +fn now() { + // open the journal entry for the current moment +} + +fn next(current_file: &str, increment: &str) { + // TODO: use into JournalIncrement + // open the journal entry for some number of intervals in the future +} + +fn prev(current_file: &str, increment: &str) { + // TODO: use into JournalIncrement + // open the journal entry for some number of intervals in the past +} + +fn jump(jump_to: &str) { + // go to some note (next monday, 12/23/2024) +} From 1e976f87890cf6b683f69e0a563b611449bf23c0 Mon Sep 17 00:00:00 2001 From: Connor Keane Date: Mon, 6 May 2024 00:21:16 -0600 Subject: [PATCH 2/7] feat: create basic jump to command --- Cargo.lock | 12 +++++ Cargo.toml | 1 + src/commands.rs | 113 +++++++++++++++++++++++------------------------- src/main.rs | 17 ++++++-- 4 files changed, 81 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74fc34f9..cdf920ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -433,6 +433,17 @@ dependencies = [ "slab", ] +[[package]] +name = "fuzzydate" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7265f35cc1f40c655aad829323a1daef5f21fd38904f6ed9bd5ec3df3cbd851c" +dependencies = [ + "chrono", + "lazy_static", + "thiserror", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -630,6 +641,7 @@ dependencies = [ "anyhow", "chrono", "config", + "fuzzydate", "indexmap", "itertools", "nanoid", diff --git a/Cargo.toml b/Cargo.toml index 0f8dfe0d..70851c5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" anyhow = "1.0.80" chrono = "0.4.35" config = "0.14.0" +fuzzydate = "0.2.2" indexmap = "2.2.6" itertools = "0.10.5" nanoid = "0.4.0" diff --git a/src/commands.rs b/src/commands.rs index 66b6da0c..f608ce5d 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,58 +1,55 @@ -use chrono::{NaiveDateTime, TimeDelta}; -use chrono::format::{Parsed, parse, strftime::StrftimeItems}; - -enum JournalIncrement { - Hours(i8), - Days(i8), - Weeks(i8), - Months(i8), - Years(i8) -} - - -fn file_to_datetime(filename: &str, format: &str) -> Result { - // default to the beginning of an underspecified interval, - // top of hour, beginning of day, start of week - - let items = StrftimeItems::new(format); - let mut parsed = Parsed::new(); - parse(&mut parsed, "2024-04-01", items); - parsed.to_naive_datetime_with_offset(0) -} - - -fn datetime_to_file(datetime: NaiveDateTime, format: &str) -> String { - datetime.format(format).to_string() -} - -fn increment_file(filename: &str, increment: JournalIncrement, format: &str) -> Result { - let current_as_datetime = file_to_datetime(filename, format); - - let next_as_datetime = match increment { - JournalIncrement::Hours(hours) => current_as_datetime.checked_add_signed(TimeDelta::hours(hours)), - JournalIncrement::Days(days) => current_as_datetime.checked_add_signed(TimeDelta::days(days)), - JournalIncrement::Weeks(weeks) => current_as_datetime.checked_add_signed(TimeDelta::weeks(weeks)), - JournalIncrement::Months(months) => current_as_datetime.checked_add_months(months), - JournalIncrement::Years(years) => current_as_datetime.checked_add_months(12*years), - } - - datetime_to_file(next_as_datetime, format) -} - -fn now() { - // open the journal entry for the current moment -} - -fn next(current_file: &str, increment: &str) { - // TODO: use into JournalIncrement - // open the journal entry for some number of intervals in the future -} - -fn prev(current_file: &str, increment: &str) { - // TODO: use into JournalIncrement - // open the journal entry for some number of intervals in the past -} - -fn jump(jump_to: &str) { - // go to some note (next monday, 12/23/2024) -} +use chrono::NaiveDateTime; +use chrono::offset::Local; +use crate::config::Settings; +use fuzzydate::parse; +use tower_lsp::lsp_types::{ShowDocumentParams, Url}; + + +// fn file_to_datetime(filename: &str, format: &str) -> Result { +// // default to the beginning of an underspecified interval, +// // top of hour, beginning of day, start of week +// +// let items = StrftimeItems::new(format); +// let mut parsed = Parsed::new(); +// parse(&mut parsed, "2024-04-01", items); +// parsed.to_naive_datetime_with_offset(0) +// } + + +fn datetime_to_file(datetime: NaiveDateTime, format: &str) -> Option { + Url::parse(&format!("file:///home/kxnr/wiki/journal/{}", &datetime.format(format).to_string())).ok() +} + +// fn increment_file(filename: &str, increment: JournalIncrement, format: &str) -> Result { +// let current_as_datetime = file_to_datetime(filename, format); +// +// let next_as_datetime = match increment { +// JournalIncrement::Hours(hours) => current_as_datetime.checked_add_signed(TimeDelta::hours(hours)), +// JournalIncrement::Days(days) => current_as_datetime.checked_add_signed(TimeDelta::days(days)), +// JournalIncrement::Weeks(weeks) => current_as_datetime.checked_add_signed(TimeDelta::weeks(weeks)), +// JournalIncrement::Months(months) => current_as_datetime.checked_add_months(months), +// JournalIncrement::Years(years) => current_as_datetime.checked_add_months(12*years), +// } +// +// datetime_to_file(next_as_datetime, format) +// } + +pub fn jump(settings: &Settings, jump_to: Option<&str>) -> Option { + // if jump_to is None, use the current time. + // TODO: special syntax to reference the current file and the current time + // TODO: make fuzzydate relative to any date + // TODO: create file + + let note_file = match jump_to { + Some(jmp_str) => parse(jmp_str).ok().and_then(|dt| datetime_to_file(dt, &settings.dailynote)), + None => datetime_to_file(Local::now().naive_local(), &settings.dailynote) + }; + + note_file.map(|uri| ShowDocumentParams{ uri, + external: Some(false), + take_focus: Some(true), + selection: None }) +} + + +// TODO; next and prev diff --git a/src/main.rs b/src/main.rs index d73567c6..ce688fc7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ use std::collections::HashSet; use std::ops::{Deref, DerefMut}; -use std::path::{Path, PathBuf}; -use std::str::FromStr; +use std::path::PathBuf; use std::sync::Arc; use completion::get_completions; @@ -24,6 +23,7 @@ use vault::Vault; mod codeactions; mod codelens; mod completion; +mod commands; mod config; mod diagnostics; mod gotodef; @@ -371,7 +371,7 @@ impl LanguageServer for Backend { resolve_provider: None, }), execute_command_provider: Some(ExecuteCommandOptions { - commands: vec!["apply_edits".into()], + commands: vec!["apply_edits".into(), "jump".into()], ..Default::default() }), semantic_tokens_provider: Some( @@ -584,7 +584,16 @@ impl LanguageServer for Backend { } Ok(None) - } + }, + ExecuteCommandParams { command, .. } if *command == *"jump" => { + let jump_to = params.arguments.first().and_then(|val| val.as_str()); + let settings = self.bind_settings(|settings| Ok(settings.to_owned())).await?; + if let Some(doc) = commands::jump(&settings, jump_to) { + self.client.show_document(doc).await?; + }; + Ok(None) + // Ok(do) + }, _ => Ok(None), } } From 85fc68d5fde90e66c54cc64aad6a111a7349c021 Mon Sep 17 00:00:00 2001 From: Connor Keane Date: Mon, 13 May 2024 23:08:57 -0600 Subject: [PATCH 3/7] cleaning up initial command implementation --- src/commands.rs | 102 +++++++++++++++++++++++++++++------------------- src/main.rs | 21 +++++----- 2 files changed, 71 insertions(+), 52 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index f608ce5d..57be2270 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,55 +1,75 @@ -use chrono::NaiveDateTime; -use chrono::offset::Local; +use std::fs::File; +use std::path::Path; + use crate::config::Settings; +use chrono::offset::Local; +use chrono::NaiveDateTime; use fuzzydate::parse; -use tower_lsp::lsp_types::{ShowDocumentParams, Url}; +use serde_json::Value; +use tower_lsp::jsonrpc::{Error, Result}; +use tower_lsp::lsp_types::{MessageType, ShowDocumentParams, Url}; +fn file_to_datetime(filename: &str, format: &str) -> Option { + // re-hydrate a datetime from a dailynote filename + todo!() +} -// fn file_to_datetime(filename: &str, format: &str) -> Result { -// // default to the beginning of an underspecified interval, -// // top of hour, beginning of day, start of week -// -// let items = StrftimeItems::new(format); -// let mut parsed = Parsed::new(); -// parse(&mut parsed, "2024-04-01", items); -// parsed.to_naive_datetime_with_offset(0) -// } - +fn datetime_to_file(datetime: NaiveDateTime, settings: &Settings) -> Option { + let filename = datetime.format(&settings.dailynote).to_string(); + let path = Path::new(&filename); -fn datetime_to_file(datetime: NaiveDateTime, format: &str) -> Option { - Url::parse(&format!("file:///home/kxnr/wiki/journal/{}", &datetime.format(format).to_string())).ok() + Url::from_file_path(path.with_extension("md")).ok() } -// fn increment_file(filename: &str, increment: JournalIncrement, format: &str) -> Result { -// let current_as_datetime = file_to_datetime(filename, format); -// -// let next_as_datetime = match increment { -// JournalIncrement::Hours(hours) => current_as_datetime.checked_add_signed(TimeDelta::hours(hours)), -// JournalIncrement::Days(days) => current_as_datetime.checked_add_signed(TimeDelta::days(days)), -// JournalIncrement::Weeks(weeks) => current_as_datetime.checked_add_signed(TimeDelta::weeks(weeks)), -// JournalIncrement::Months(months) => current_as_datetime.checked_add_months(months), -// JournalIncrement::Years(years) => current_as_datetime.checked_add_months(12*years), -// } -// -// datetime_to_file(next_as_datetime, format) -// } - -pub fn jump(settings: &Settings, jump_to: Option<&str>) -> Option { +pub async fn jump( + client: &tower_lsp::Client, + settings: &Settings, + jump_to: Option<&str>, +) -> Result> { // if jump_to is None, use the current time. - // TODO: special syntax to reference the current file and the current time - // TODO: make fuzzydate relative to any date - // TODO: create file let note_file = match jump_to { - Some(jmp_str) => parse(jmp_str).ok().and_then(|dt| datetime_to_file(dt, &settings.dailynote)), - None => datetime_to_file(Local::now().naive_local(), &settings.dailynote) + Some(jmp_str) => parse(jmp_str) + .ok() + .and_then(|dt| datetime_to_file(dt, &settings)), + None => datetime_to_file(Local::now().naive_local(), &settings), }; - note_file.map(|uri| ShowDocumentParams{ uri, - external: Some(false), - take_focus: Some(true), - selection: None }) -} + if let Some(uri) = note_file { + // file creation can fail and return an Err, ignore this and try + // to open the file on the off chance the client knows what to do + // TODO: log failure to create file + let _ = uri.to_file_path().map(|path| { + path.parent().map(|parent| std::fs::create_dir_all(parent)); + File::create_new(path.as_path().to_owned()) + }); + client + .show_document(ShowDocumentParams { + uri, + external: Some(false), + take_focus: Some(true), + selection: None, + }) + .await + .map(|success| Some(success.into())) + } else { + client + .log_message( + MessageType::ERROR, + format!("could not parse {jump_to:?}: {:?}", jump_to.map(parse)), + ) + .await; + Err(Error::invalid_params( + "Could not parse journal format as a valid uri.".to_string(), + )) + } +} -// TODO; next and prev +pub fn jump_relative( + client: tower_lsp::Client, + settings: &Settings, + jump_to: &str, +) -> Result> { + todo!("pending PR in fuzzydate to specify base time") +} diff --git a/src/main.rs b/src/main.rs index ce688fc7..28f7315d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,8 +22,8 @@ use vault::Vault; mod codeactions; mod codelens; -mod completion; mod commands; +mod completion; mod config; mod diagnostics; mod gotodef; @@ -291,9 +291,10 @@ impl Backend { #[tower_lsp::async_trait] impl LanguageServer for Backend { async fn initialize(&self, i: InitializeParams) -> Result { - let root_dir = match i.root_uri { - Some(uri) => uri.to_file_path().or(Err(Error::new(ErrorCode::InvalidParams)))?, + Some(uri) => uri + .to_file_path() + .or(Err(Error::new(ErrorCode::InvalidParams)))?, None => std::env::current_dir().or(Err(Error::new(ErrorCode::InvalidParams)))?, }; @@ -584,16 +585,14 @@ impl LanguageServer for Backend { } Ok(None) - }, + } ExecuteCommandParams { command, .. } if *command == *"jump" => { let jump_to = params.arguments.first().and_then(|val| val.as_str()); - let settings = self.bind_settings(|settings| Ok(settings.to_owned())).await?; - if let Some(doc) = commands::jump(&settings, jump_to) { - self.client.show_document(doc).await?; - }; - Ok(None) - // Ok(do) - }, + let settings = self + .bind_settings(|settings| Ok(settings.to_owned())) + .await?; + commands::jump(&self.client, &settings, jump_to).await + } _ => Ok(None), } } From 5b73ce2d5e25f13f326a627002de4b06356a215c Mon Sep 17 00:00:00 2001 From: Felix Zeller Date: Sun, 19 May 2024 18:00:41 -0400 Subject: [PATCH 4/7] working command --- src/commands.rs | 43 ++++++++++++++++++++++++++++++++++++------- src/main.rs | 3 ++- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 57be2270..f8e333d1 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -2,6 +2,7 @@ use std::fs::File; use std::path::Path; use crate::config::Settings; +use crate::vault::{self, Vault}; use chrono::offset::Local; use chrono::NaiveDateTime; use fuzzydate::parse; @@ -14,25 +15,29 @@ fn file_to_datetime(filename: &str, format: &str) -> Option { todo!() } -fn datetime_to_file(datetime: NaiveDateTime, settings: &Settings) -> Option { - let filename = datetime.format(&settings.dailynote).to_string(); - let path = Path::new(&filename); +fn datetime_to_file(datetime: NaiveDateTime, dailynote_format: &str, root_dir: &Path) -> Option { + let filename = datetime.format(dailynote_format).to_string(); + let path = root_dir.join(&filename); + + println!("path: {:?}", path); Url::from_file_path(path.with_extension("md")).ok() } pub async fn jump( client: &tower_lsp::Client, + root_dir: &Path, settings: &Settings, jump_to: Option<&str>, ) -> Result> { // if jump_to is None, use the current time. + let daily_note_format = &settings.dailynote; let note_file = match jump_to { Some(jmp_str) => parse(jmp_str) .ok() - .and_then(|dt| datetime_to_file(dt, &settings)), - None => datetime_to_file(Local::now().naive_local(), &settings), + .and_then(|dt| datetime_to_file(dt, &daily_note_format, root_dir)), + None => datetime_to_file(Local::now().naive_local(), &daily_note_format, root_dir), }; if let Some(uri) = note_file { @@ -41,7 +46,9 @@ pub async fn jump( // TODO: log failure to create file let _ = uri.to_file_path().map(|path| { path.parent().map(|parent| std::fs::create_dir_all(parent)); - File::create_new(path.as_path().to_owned()) + if !path.exists() { + let _ = File::create(path.as_path().to_owned()); + } }); client @@ -61,7 +68,7 @@ pub async fn jump( ) .await; Err(Error::invalid_params( - "Could not parse journal format as a valid uri.".to_string(), + format!("Could not parse journal format ({jump_to:?}) as a valid uri: {:?}.", jump_to.map(parse)), )) } } @@ -73,3 +80,25 @@ pub fn jump_relative( ) -> Result> { todo!("pending PR in fuzzydate to specify base time") } + +// tests +#[cfg(test)] +mod tests { + use fuzzydate::parse; + + use crate::config::Settings; + + use super::datetime_to_file; + + + #[test] + fn test_string_to_file() { + + let input = "today"; + + let parsed_datetime = parse(input).unwrap(); + + let file = datetime_to_file(parsed_datetime, "%Y-%m-%d", &std::fs::canonicalize("./").unwrap()).unwrap(); + + } +} diff --git a/src/main.rs b/src/main.rs index 28f7315d..d2a97d28 100644 --- a/src/main.rs +++ b/src/main.rs @@ -591,7 +591,8 @@ impl LanguageServer for Backend { let settings = self .bind_settings(|settings| Ok(settings.to_owned())) .await?; - commands::jump(&self.client, &settings, jump_to).await + let root_dir = self.bind_vault(|vault| Ok(vault.root_dir().to_owned())).await?; + commands::jump(&self.client, &root_dir, &settings, jump_to).await } _ => Ok(None), } From a62ebe18a0e7fe9b02b5c018f785a2b10ee67dd5 Mon Sep 17 00:00:00 2001 From: Felix Zeller Date: Sun, 19 May 2024 18:06:21 -0400 Subject: [PATCH 5/7] remove unused --- src/commands.rs | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index f8e333d1..8172b63d 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -2,7 +2,6 @@ use std::fs::File; use std::path::Path; use crate::config::Settings; -use crate::vault::{self, Vault}; use chrono::offset::Local; use chrono::NaiveDateTime; use fuzzydate::parse; @@ -10,11 +9,6 @@ use serde_json::Value; use tower_lsp::jsonrpc::{Error, Result}; use tower_lsp::lsp_types::{MessageType, ShowDocumentParams, Url}; -fn file_to_datetime(filename: &str, format: &str) -> Option { - // re-hydrate a datetime from a dailynote filename - todo!() -} - fn datetime_to_file(datetime: NaiveDateTime, dailynote_format: &str, root_dir: &Path) -> Option { let filename = datetime.format(dailynote_format).to_string(); let path = root_dir.join(&filename); @@ -73,21 +67,11 @@ pub async fn jump( } } -pub fn jump_relative( - client: tower_lsp::Client, - settings: &Settings, - jump_to: &str, -) -> Result> { - todo!("pending PR in fuzzydate to specify base time") -} - // tests #[cfg(test)] mod tests { use fuzzydate::parse; - use crate::config::Settings; - use super::datetime_to_file; @@ -98,7 +82,7 @@ mod tests { let parsed_datetime = parse(input).unwrap(); - let file = datetime_to_file(parsed_datetime, "%Y-%m-%d", &std::fs::canonicalize("./").unwrap()).unwrap(); + let _ = datetime_to_file(parsed_datetime, "%Y-%m-%d", &std::fs::canonicalize("./").unwrap()).unwrap(); } } From d9851b6738b159e0394b02d23e9e7054bc4293d8 Mon Sep 17 00:00:00 2001 From: Felix Zeller Date: Sun, 19 May 2024 19:25:54 -0400 Subject: [PATCH 6/7] Add documentation for setting up the daily note command --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index 603f41f8..425638ab 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,30 @@ Markdown Oxide's features are implemented in the form of a language server aimin + -
+ (optional) Enable opening daily notes with natural langauge + + Modify your lsp `on_attach` function to support opening daily notes with, for example, `:Daily two days ago` or `:Daily next monday` + + ```lua + -- setup Markdown Oxide daily note commands + if client.name == "markdown_oxide" then + + vim.api.nvim_create_user_command( + "Daily", + function(args) + local input = args.args + + vim.lsp.buf.execute_command({command="jump", arguments={input}}) + + end, + {desc = 'Open daily note', nargs = "*"} + ) + end + ``` + +
+ ### VSCode From ccce5e721bcaea9b1613eb646395e8defbeef115 Mon Sep 17 00:00:00 2001 From: Felix Zeller Date: Sun, 19 May 2024 20:56:37 -0400 Subject: [PATCH 7/7] link to fuzzydate --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 425638ab..aaa756ea 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ Markdown Oxide's features are implemented in the form of a language server aimin -
(optional) Enable opening daily notes with natural langauge - Modify your lsp `on_attach` function to support opening daily notes with, for example, `:Daily two days ago` or `:Daily next monday` + Modify your lsp `on_attach` function to support opening daily notes with, for example, `:Daily two days ago` or `:Daily next monday`. The specification can be found [here](https://docs.rs/fuzzydate/latest/fuzzydate/) ```lua -- setup Markdown Oxide daily note commands