@@ -1,70 +1,124 @@
1−use chrono::{Duration, NaiveDate};
1+use gregorian::{Date, DateResultExt};
3use std::borrow::Cow;
4use std::borrow::Cow;
5use ropey::RopeSlice;
6use ropey::RopeSlice;
8− textobject::{textobject_word, TextObject},
9− Range, Tendril,
8+use crate::{Range, Tendril};
10+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
12// Only support formats that aren't region specific.
16// Only support formats that aren't region specific.
13−static FORMATS: &[&str] = &["%Y-%m-%d", "%Y/%m/%d"];
17+static FORMATS: &[Format] = &[
19+ regex: r"(\d{4})-(\d{2})-(\d{2})",
23+ regex: r"(\d{4})/(\d{2})/(\d{2})",
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.
18const DATE_LENGTH: usize = 10;
28const DATE_LENGTH: usize = 10;
30+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
20#[derive(Debug, PartialEq, Eq)]
37#[derive(Debug, PartialEq, Eq)]
21pub struct DateIncrementor {
38pub struct DateIncrementor {
22− pub date: NaiveDate,
39+ pub date: Date,
23 pub range: Range,
40 pub range: Range,
24− pub format: &'static str,
43+ format: Format,
27impl DateIncrementor {
46impl DateIncrementor {
28 pub fn from_range(text: RopeSlice, range: Range) -> Option<DateIncrementor> {
47 pub fn from_range(text: RopeSlice, range: Range) -> Option<DateIncrementor> {
29− // Don't increment if the cursor is one right of the date text.
48+ let from = range.from().saturating_sub(DATE_LENGTH);
30− if text.char(range.from()).is_whitespace() {
49+ let to = (range.from() + DATE_LENGTH).min(text.len_chars());
31− return None;
50+ let (from_in_text, to_in_text) = (range.from() - from, range.to() - from);
32− }
51+ let text: Cow<str> = text.slice(from..to).into();
34− let range = textobject_word(text, range, TextObject::Inside, 1, true);
53+ FORMATS.iter().find_map(|&format| {
35− let text: Cow<str> = text.slice(range.from()..range.to()).into();
54+ let re = Regex::new(format.regex).ok()?;
55+ let captures = re.captures(&text)?;
37− let first = text.chars().next()?;
57+ let date = captures.get(0)?;
38− let last = text.chars().next_back()?;
58+ let offset = range.from() - from_in_text;
59+ let range = Range::new(date.start() + offset, date.end() + offset);
40− // Allow date strings in quotes.
61+ let year = captures.get(1)?;
41− let (range, text) = if first == last && (first == '"' || first == '\'') {
62+ let month = captures.get(2)?;
42− (
63+ let day = captures.get(3)?;
43− Range::new(range.from() + 1, range.to() - 1),
44− Cow::from(&text[1..text.len() - 1]),
50− if text.len() != DATE_LENGTH {
65+ let year_range = year.range();
51− return None;
66+ let month_range = month.range();
52− }
67+ let day_range = day.range();
54− FORMATS.iter().find_map(|format| {
69+ let to_inclusive = if to_in_text > from_in_text {
55− NaiveDate::parse_from_str(&text, format)
70+ to_in_text - 1
74+ let field = if year_range.contains(&from_in_text) && year_range.contains(&to_inclusive)
77+ } else if month_range.contains(&from_in_text) && month_range.contains(&to_inclusive) {
79+ } else if day_range.contains(&from_in_text) && day_range.contains(&to_inclusive) {
85+ let year: i16 = year.as_str().parse().ok()?;
86+ let month: u8 = month.as_str().parse().ok()?;
87+ let day: u8 = day.as_str().parse().ok()?;
89+ let date = Date::new(year, month, day).ok()?;
57− .map(|date| DateIncrementor {
91+ Some(DateIncrementor {
65 pub fn incremented_text(&self, amount: i64) -> Tendril {
100 pub fn incremented_text(&self, amount: i64) -> Tendril {
66− let incremented_date = self.date + Duration::days(amount);
101+ let date = match self.field {
67− incremented_date.format(self.format).to_string().into()
102+ DateField::Year => self
104+ .add_years(amount.try_into().unwrap_or(0))
106+ DateField::Month => self
108+ .add_months(amount.try_into().unwrap_or(0))
110+ DateField::Day => self.date.add_days(amount.try_into().unwrap_or(0)),
114+ "{:04}{}{:02}{}{:02}",
116+ self.format.separator,
117+ date.month().to_number(),
118+ self.format.separator,
@@ -74,43 +128,144 @@ mod test {
74 use crate::Rope;
128 use crate::Rope;
77− fn test_date_dashes() {
131+ fn test_create_incrementor_for_year_with_dashes() {
78 let rope = Rope::from_str("2021-11-15");
132 let rope = Rope::from_str("2021-11-15");
79− let range = Range::point(0);
135+ let range = Range::point(head);
137+ DateIncrementor::from_range(rope.slice(..), range),
138+ Some(DateIncrementor {
139+ date: Date::new(2021, 11, 15).unwrap(),
140+ range: Range::new(0, 10),
141+ field: DateField::Year,
149+ fn test_create_incrementor_for_month_with_dashes() {
150+ let rope = Rope::from_str("2021-11-15");
153+ let range = Range::point(head);
80− assert_eq!(
154+ assert_eq!(
81− DateIncrementor::from_range(rope.slice(..), range),
155+ DateIncrementor::from_range(rope.slice(..), range),
82− Some(DateIncrementor {
156+ Some(DateIncrementor {
83− date: NaiveDate::from_ymd(2021, 11, 15),
157+ date: Date::new(2021, 11, 15).unwrap(),
84− range: Range::new(0, 10),
158+ range: Range::new(0, 10),
159+ field: DateField::Month,
85− format: "%Y-%m-%d",
160+ format: FORMATS[0],
167+ fn test_create_incrementor_for_day_with_dashes() {
168+ let rope = Rope::from_str("2021-11-15");
171+ let range = Range::point(head);
173+ DateIncrementor::from_range(rope.slice(..), range),
174+ Some(DateIncrementor {
175+ date: Date::new(2021, 11, 15).unwrap(),
176+ range: Range::new(0, 10),
177+ field: DateField::Day,
91− fn test_date_slashes() {
185+ fn test_try_create_incrementor_on_dashes() {
186+ let rope = Rope::from_str("2021-11-15");
188+ for head in &[4, 7] {
189+ let range = Range::point(*head);
190+ assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None,);
195+ fn test_create_incrementor_for_year_with_slashes() {
92 let rope = Rope::from_str("2021/11/15");
196 let rope = Rope::from_str("2021/11/15");
93− let range = Range::point(0);
199+ let range = Range::point(head);
201+ DateIncrementor::from_range(rope.slice(..), range),
202+ Some(DateIncrementor {
203+ date: Date::new(2021, 11, 15).unwrap(),
204+ range: Range::new(0, 10),
205+ field: DateField::Year,
213+ fn test_create_incrementor_for_month_with_slashes() {
214+ let rope = Rope::from_str("2021/11/15");
217+ let range = Range::point(head);
94− assert_eq!(
218+ assert_eq!(
95− DateIncrementor::from_range(rope.slice(..), range),
219+ DateIncrementor::from_range(rope.slice(..), range),
96− Some(DateIncrementor {
220+ Some(DateIncrementor {
97− date: NaiveDate::from_ymd(2021, 11, 15),
221+ date: Date::new(2021, 11, 15).unwrap(),
98− range: Range::new(0, 10),
222+ range: Range::new(0, 10),
223+ field: DateField::Month,
99− format: "%Y/%m/%d",
224+ format: FORMATS[1],
231+ fn test_create_incrementor_for_day_with_slashes() {
232+ let rope = Rope::from_str("2021/11/15");
235+ let range = Range::point(head);
237+ DateIncrementor::from_range(rope.slice(..), range),
238+ Some(DateIncrementor {
239+ date: Date::new(2021, 11, 15).unwrap(),
240+ range: Range::new(0, 10),
241+ field: DateField::Day,
249+ fn test_try_create_incrementor_on_slashes() {
250+ let rope = Rope::from_str("2021/11/15");
252+ for head in &[4, 7] {
253+ let range = Range::point(*head);
254+ assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None,);
105 fn test_date_surrounded_by_spaces() {
259 fn test_date_surrounded_by_spaces() {
106 let rope = Rope::from_str(" 2021-11-15 ");
260 let rope = Rope::from_str(" 2021-11-15 ");
107− let range = Range::point(10);
261+ let range = Range::point(3);
108 assert_eq!(
262 assert_eq!(
109 DateIncrementor::from_range(rope.slice(..), range),
263 DateIncrementor::from_range(rope.slice(..), range),
110 Some(DateIncrementor {
264 Some(DateIncrementor {
111− date: NaiveDate::from_ymd(2021, 11, 15),
265+ date: Date::new(2021, 11, 15).unwrap(),
112 range: Range::new(3, 13),
266 range: Range::new(3, 13),
267+ field: DateField::Year,
113− format: "%Y-%m-%d",
268+ format: FORMATS[0],
@@ -122,23 +277,25 @@ mod test {
122 assert_eq!(
277 assert_eq!(
123 DateIncrementor::from_range(rope.slice(..), range),
278 DateIncrementor::from_range(rope.slice(..), range),
124 Some(DateIncrementor {
279 Some(DateIncrementor {
125− date: NaiveDate::from_ymd(2021, 11, 15),
280+ date: Date::new(2021, 11, 15).unwrap(),
126 range: Range::new(8, 18),
281 range: Range::new(8, 18),
282+ field: DateField::Year,
127− format: "%Y-%m-%d",
283+ format: FORMATS[0],
133 fn test_date_in_double_quotes() {
289 fn test_date_in_double_quotes() {
134− let rope = Rope::from_str("date = \"2021-11-15\"");
290+ let rope = Rope::from_str("let date = \"2021-11-15\";");
135− let range = Range::point(10);
291+ let range = Range::point(12);
136 assert_eq!(
292 assert_eq!(
137 DateIncrementor::from_range(rope.slice(..), range),
293 DateIncrementor::from_range(rope.slice(..), range),
138 Some(DateIncrementor {
294 Some(DateIncrementor {
139− date: NaiveDate::from_ymd(2021, 11, 15),
295+ date: Date::new(2021, 11, 15).unwrap(),
140− range: Range::new(8, 18),
296+ range: Range::new(12, 22),
297+ field: DateField::Year,
141− format: "%Y-%m-%d",
298+ format: FORMATS[0],
@@ -189,23 +346,28 @@ mod test {
190 fn test_increment_dates() {
347 fn test_increment_dates() {
191 let tests = [
348 let tests = [
192− ("1980-12-21", 1, "1980-12-22"),
349+ // (original, cursor, amount, expected)
193− ("1980-12-21", -1, "1980-12-20"),
350+ ("2020-02-28", 0, 1, "2021-02-28"),
194− ("1980-12-21", 100, "1981-03-31"),
351+ ("2020-02-29", 0, 1, "2021-03-01"),
195− ("1980-12-21", -100, "1980-09-12"),
352+ ("2020-01-31", 5, 1, "2020-02-29"),
196− ("1980-12-21", 1000, "1983-09-17"),
353+ ("2020-01-20", 5, 1, "2020-02-20"),
197− ("1980-12-21", -1000, "1978-03-27"),
354+ ("2020-02-28", 8, 1, "2020-02-29"),
198− ("1980/12/21", 1, "1980/12/22"),
355+ ("2021-02-28", 8, 1, "2021-03-01"),
199− ("1980/12/21", -1, "1980/12/20"),
356+ ("2021-02-28", 0, -1, "2020-02-28"),
357+ ("2021-03-01", 0, -1, "2020-03-01"),
358+ ("2020-02-29", 5, -1, "2020-01-29"),
359+ ("2020-02-20", 5, -1, "2020-01-20"),
360+ ("2020-02-29", 8, -1, "2020-02-28"),
361+ ("2021-03-01", 8, -1, "2021-02-28"),
200− ("1980/12/21", 100, "1981/03/31"),
362+ ("1980/12/21", 8, 100, "1981/03/31"),
201− ("1980/12/21", -100, "1980/09/12"),
363+ ("1980/12/21", 8, -100, "1980/09/12"),
202− ("1980/12/21", 1000, "1983/09/17"),
364+ ("1980/12/21", 8, 1000, "1983/09/17"),
203− ("1980/12/21", -1000, "1978/03/27"),
365+ ("1980/12/21", 8, -1000, "1978/03/27"),
206− for (original, amount, expected) in tests {
368+ for (original, cursor, amount, expected) in tests {
207 let rope = Rope::from_str(original);
369 let rope = Rope::from_str(original);
208− let range = Range::point(0);
370+ let range = Range::point(cursor);
209 assert_eq!(
371 assert_eq!(
210 DateIncrementor::from_range(rope.slice(..), range)
372 DateIncrementor::from_range(rope.slice(..), range)
211 .unwrap()
373 .unwrap()