@@ -0,0 +1,217 @@
1+use chrono::{Duration, NaiveDate};
8+ textobject::{textobject_word, TextObject},
12+// Only support formats that aren't region specific.
13+static FORMATS: &[&str] = &["%Y-%m-%d", "%Y/%m/%d"];
15+// We don't want to parse ambiguous dates like 10/11/12 or 7/8/10.
16+// They must be YYYY-mm-dd or YYYY/mm/dd.
17+// So 2021-01-05 works, but 2021-1-5 doesn't.
18+const DATE_LENGTH: usize = 10;
20+#[derive(Debug, PartialEq, Eq)]
21+pub struct DateIncrementor {
24+ pub format: &'static str,
27+impl DateIncrementor {
28+ pub fn from_range(text: RopeSlice, range: Range) -> Option<DateIncrementor> {
29+ // Don't increment if the cursor is one right of the date text.
30+ if text.char(range.from()).is_whitespace() {
34+ let range = textobject_word(text, range, TextObject::Inside, 1, true);
35+ let text: Cow<str> = text.slice(range.from()..range.to()).into();
37+ let first = text.chars().next()?;
38+ let last = text.chars().next_back()?;
40+ // Allow date strings in quotes.
41+ let (range, text) = if first == last && (first == '"' || first == '\'') {
43+ Range::new(range.from() + 1, range.to() - 1),
44+ Cow::from(&text[1..text.len() - 1]),
50+ if text.len() != DATE_LENGTH {
54+ FORMATS.iter().find_map(|format| {
55+ NaiveDate::parse_from_str(&text, format)
57+ .map(|date| DateIncrementor {
65+ pub fn incremented_text(&self, amount: i64) -> Tendril {
66+ let incremented_date = self.date + Duration::days(amount);
67+ incremented_date.format(self.format).to_string().into()
77+ fn test_date_dashes() {
78+ let rope = Rope::from_str("2021-11-15");
79+ let range = Range::point(0);
81+ DateIncrementor::from_range(rope.slice(..), range),
82+ Some(DateIncrementor {
83+ date: NaiveDate::from_ymd(2021, 11, 15),
84+ range: Range::new(0, 10),
91+ fn test_date_slashes() {
92+ let rope = Rope::from_str("2021/11/15");
93+ let range = Range::point(0);
95+ DateIncrementor::from_range(rope.slice(..), range),
96+ Some(DateIncrementor {
97+ date: NaiveDate::from_ymd(2021, 11, 15),
98+ range: Range::new(0, 10),
105+ fn test_date_surrounded_by_spaces() {
106+ let rope = Rope::from_str(" 2021-11-15 ");
107+ let range = Range::point(10);
109+ DateIncrementor::from_range(rope.slice(..), range),
110+ Some(DateIncrementor {
111+ date: NaiveDate::from_ymd(2021, 11, 15),
112+ range: Range::new(3, 13),
119+ fn test_date_in_single_quotes() {
120+ let rope = Rope::from_str("date = '2021-11-15'");
121+ let range = Range::point(10);
123+ DateIncrementor::from_range(rope.slice(..), range),
124+ Some(DateIncrementor {
125+ date: NaiveDate::from_ymd(2021, 11, 15),
126+ range: Range::new(8, 18),
133+ fn test_date_in_double_quotes() {
134+ let rope = Rope::from_str("date = \"2021-11-15\"");
135+ let range = Range::point(10);
137+ DateIncrementor::from_range(rope.slice(..), range),
138+ Some(DateIncrementor {
139+ date: NaiveDate::from_ymd(2021, 11, 15),
140+ range: Range::new(8, 18),
147+ fn test_date_cursor_one_right_of_date() {
148+ let rope = Rope::from_str("2021-11-15 ");
149+ let range = Range::point(10);
150+ assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None);
154+ fn test_date_cursor_one_left_of_number() {
155+ let rope = Rope::from_str(" 2021-11-15");
156+ let range = Range::point(0);
157+ assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None);
161+ fn test_invalid_dates() {
181+ for invalid in tests {
182+ let rope = Rope::from_str(invalid);
183+ let range = Range::point(0);
185+ assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None);
190+ fn test_increment_dates() {
192+ ("1980-12-21", 1, "1980-12-22"),
193+ ("1980-12-21", -1, "1980-12-20"),
194+ ("1980-12-21", 100, "1981-03-31"),
195+ ("1980-12-21", -100, "1980-09-12"),
196+ ("1980-12-21", 1000, "1983-09-17"),
197+ ("1980-12-21", -1000, "1978-03-27"),
198+ ("1980/12/21", 1, "1980/12/22"),
199+ ("1980/12/21", -1, "1980/12/20"),
200+ ("1980/12/21", 100, "1981/03/31"),
201+ ("1980/12/21", -100, "1980/09/12"),
202+ ("1980/12/21", 1000, "1983/09/17"),
203+ ("1980/12/21", -1000, "1978/03/27"),
206+ for (original, amount, expected) in tests {
207+ let rope = Rope::from_str(original);
208+ let range = Range::point(0);
210+ DateIncrementor::from_range(rope.slice(..), range)
212+ .incremented_text(amount),