From c9641fccedc51737a74ed47009279fa688462ea9 Mon Sep 17 00:00:00 2001 From: Jason Rodney Hansen Date: Sun, 21 Nov 2021 10:38:41 -0700 Subject: Add `Increment` trait --- helix-core/src/increment/date.rs | 474 ++++++++++++++++++++++++++++++++++ helix-core/src/increment/mod.rs | 8 + helix-core/src/increment/number.rs | 507 +++++++++++++++++++++++++++++++++++++ 3 files changed, 989 insertions(+) create mode 100644 helix-core/src/increment/date.rs create mode 100644 helix-core/src/increment/mod.rs create mode 100644 helix-core/src/increment/number.rs (limited to 'helix-core/src/increment') diff --git a/helix-core/src/increment/date.rs b/helix-core/src/increment/date.rs new file mode 100644 index 00000000..05442990 --- /dev/null +++ b/helix-core/src/increment/date.rs @@ -0,0 +1,474 @@ +use regex::Regex; + +use std::borrow::Cow; +use std::cmp; + +use ropey::RopeSlice; + +use crate::{Range, Tendril}; + +use chrono::{Datelike, Duration, NaiveDate}; + +use super::Increment; + +fn ndays_in_month(year: i32, month: u32) -> u32 { + // The first day of the next month... + let (y, m) = if month == 12 { + (year + 1, 1) + } else { + (year, month + 1) + }; + let d = NaiveDate::from_ymd(y, m, 1); + + // ...is preceded by the last day of the original month. + d.pred().day() +} + +fn add_days(date: NaiveDate, amount: i64) -> Option { + date.checked_add_signed(Duration::days(amount)) +} + +fn add_months(date: NaiveDate, amount: i64) -> Option { + let month = date.month0() as i64 + amount; + let year = date.year() + i32::try_from(month / 12).ok()?; + let year = if month.is_negative() { year - 1 } else { year }; + + // Normalize month + let month = month % 12; + let month = if month.is_negative() { + month + 13 + } else { + month + 1 + } as u32; + + let day = cmp::min(date.day(), ndays_in_month(year, month)); + + Some(NaiveDate::from_ymd(year, month, day)) +} + +fn add_years(date: NaiveDate, amount: i64) -> Option { + let year = i32::try_from(date.year() as i64 + amount).ok()?; + let ndays = ndays_in_month(year, date.month()); + + if date.day() > ndays { + let d = NaiveDate::from_ymd(year, date.month(), ndays); + Some(d.succ()) + } else { + date.with_year(year) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +struct Format { + regex: &'static str, + separator: char, +} + +// Only support formats that aren't region specific. +static FORMATS: &[Format] = &[ + Format { + regex: r"(\d{4})-(\d{2})-(\d{2})", + separator: '-', + }, + Format { + regex: r"(\d{4})/(\d{2})/(\d{2})", + separator: '/', + }, +]; + +const DATE_LENGTH: usize = 10; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum DateField { + Year, + Month, + Day, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct DateIncrementor { + date: NaiveDate, + range: Range, + field: DateField, + format: Format, +} + +impl DateIncrementor { + pub fn from_range(text: RopeSlice, range: Range) -> Option { + let range = if range.is_empty() { + if range.anchor < text.len_bytes() { + // Treat empty range as a cursor range. + range.put_cursor(text, range.anchor + 1, true) + } else { + // The range is empty and at the end of the text. + return None; + } + } else { + range + }; + + let from = range.from().saturating_sub(DATE_LENGTH); + let to = (range.from() + DATE_LENGTH).min(text.len_chars()); + + let (from_in_text, to_in_text) = (range.from() - from, range.to() - from); + let text: Cow = text.slice(from..to).into(); + + FORMATS.iter().find_map(|&format| { + let re = Regex::new(format.regex).ok()?; + let captures = re.captures(&text)?; + + let date = captures.get(0)?; + let offset = range.from() - from_in_text; + let range = Range::new(date.start() + offset, date.end() + offset); + + let (year, month, day) = (captures.get(1)?, captures.get(2)?, captures.get(3)?); + let (year_range, month_range, day_range) = (year.range(), month.range(), day.range()); + + let field = if year_range.contains(&from_in_text) + && year_range.contains(&(to_in_text - 1)) + { + DateField::Year + } else if month_range.contains(&from_in_text) && month_range.contains(&(to_in_text - 1)) + { + DateField::Month + } else if day_range.contains(&from_in_text) && day_range.contains(&(to_in_text - 1)) { + DateField::Day + } else { + return None; + }; + + let date = NaiveDate::from_ymd_opt( + year.as_str().parse::().ok()?, + month.as_str().parse::().ok()?, + day.as_str().parse::().ok()?, + )?; + + Some(DateIncrementor { + date, + field, + range, + format, + }) + }) + } +} + +impl Increment for DateIncrementor { + fn increment(&self, amount: i64) -> (Range, Tendril) { + let date = match self.field { + DateField::Year => add_years(self.date, amount), + DateField::Month => add_months(self.date, amount), + DateField::Day => add_days(self.date, amount), + } + .unwrap_or(self.date); + + ( + self.range, + format!( + "{:04}{}{:02}{}{:02}", + date.year(), + self.format.separator, + date.month(), + self.format.separator, + date.day() + ) + .into(), + ) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::Rope; + + #[test] + fn test_create_incrementor_for_year_with_dashes() { + let rope = Rope::from_str("2021-11-15"); + + for cursor in 0..=3 { + let range = Range::new(cursor, cursor + 1); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(0, 10), + field: DateField::Year, + format: FORMATS[0], + }) + ); + } + } + + #[test] + fn test_create_incrementor_for_month_with_dashes() { + let rope = Rope::from_str("2021-11-15"); + + for cursor in 5..=6 { + let range = Range::new(cursor, cursor + 1); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(0, 10), + field: DateField::Month, + format: FORMATS[0], + }) + ); + } + } + + #[test] + fn test_create_incrementor_for_day_with_dashes() { + let rope = Rope::from_str("2021-11-15"); + + for cursor in 8..=9 { + let range = Range::new(cursor, cursor + 1); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(0, 10), + field: DateField::Day, + format: FORMATS[0], + }) + ); + } + } + + #[test] + fn test_try_create_incrementor_on_dashes() { + let rope = Rope::from_str("2021-11-15"); + + for &cursor in &[4, 7] { + let range = Range::new(cursor, cursor + 1); + assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None,); + } + } + + #[test] + fn test_create_incrementor_for_year_with_slashes() { + let rope = Rope::from_str("2021/11/15"); + + for cursor in 0..=3 { + let range = Range::new(cursor, cursor + 1); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(0, 10), + field: DateField::Year, + format: FORMATS[1], + }) + ); + } + } + + #[test] + fn test_create_incrementor_for_month_with_slashes() { + let rope = Rope::from_str("2021/11/15"); + + for cursor in 5..=6 { + let range = Range::new(cursor, cursor + 1); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(0, 10), + field: DateField::Month, + format: FORMATS[1], + }) + ); + } + } + + #[test] + fn test_create_incrementor_for_day_with_slashes() { + let rope = Rope::from_str("2021/11/15"); + + for cursor in 8..=9 { + let range = Range::new(cursor, cursor + 1); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(0, 10), + field: DateField::Day, + format: FORMATS[1], + }) + ); + } + } + + #[test] + fn test_try_create_incrementor_on_slashes() { + let rope = Rope::from_str("2021/11/15"); + + for &cursor in &[4, 7] { + let range = Range::new(cursor, cursor + 1); + assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None,); + } + } + + #[test] + fn test_date_surrounded_by_spaces() { + let rope = Rope::from_str(" 2021-11-15 "); + let range = Range::new(3, 4); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(3, 13), + field: DateField::Year, + format: FORMATS[0], + }) + ); + } + + #[test] + fn test_date_in_single_quotes() { + let rope = Rope::from_str("date = '2021-11-15'"); + let range = Range::new(10, 11); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(8, 18), + field: DateField::Year, + format: FORMATS[0], + }) + ); + } + + #[test] + fn test_date_in_double_quotes() { + let rope = Rope::from_str("let date = \"2021-11-15\";"); + let range = Range::new(12, 13); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(12, 22), + field: DateField::Year, + format: FORMATS[0], + }) + ); + } + + #[test] + fn test_date_cursor_one_right_of_date() { + let rope = Rope::from_str("2021-11-15 "); + let range = Range::new(10, 11); + assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None); + } + + #[test] + fn test_date_cursor_one_left_of_number() { + let rope = Rope::from_str(" 2021-11-15"); + let range = Range::new(0, 1); + assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None); + } + + #[test] + fn test_date_empty_range_at_beginning() { + let rope = Rope::from_str("2021-11-15"); + let range = Range::point(0); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(0, 10), + field: DateField::Year, + format: FORMATS[0], + }) + ); + } + + #[test] + fn test_date_empty_range_at_in_middle() { + let rope = Rope::from_str("2021-11-15"); + let range = Range::point(5); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(0, 10), + field: DateField::Month, + format: FORMATS[0], + }) + ); + } + + #[test] + fn test_date_empty_range_at_end() { + let rope = Rope::from_str("2021-11-15"); + let range = Range::point(10); + assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None); + } + + #[test] + fn test_invalid_dates() { + let tests = [ + "0000-00-00", + "1980-2-21", + "1980-12-1", + "12345", + "2020-02-30", + "1999-12-32", + "19-12-32", + "1-2-3", + "0000/00/00", + "1980/2/21", + "1980/12/1", + "12345", + "2020/02/30", + "1999/12/32", + "19/12/32", + "1/2/3", + ]; + + for invalid in tests { + let rope = Rope::from_str(invalid); + let range = Range::new(0, 1); + + assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None); + } + } + + #[test] + fn test_increment_dates() { + let tests = [ + // (original, cursor, amount, expected) + ("2020-02-28", 0, 1, "2021-02-28"), + ("2020-02-29", 0, 1, "2021-03-01"), + ("2020-01-31", 5, 1, "2020-02-29"), + ("2020-01-20", 5, 1, "2020-02-20"), + ("2021-01-01", 5, -1, "2020-12-01"), + ("2021-01-31", 5, -2, "2020-11-30"), + ("2020-02-28", 8, 1, "2020-02-29"), + ("2021-02-28", 8, 1, "2021-03-01"), + ("2021-02-28", 0, -1, "2020-02-28"), + ("2021-03-01", 0, -1, "2020-03-01"), + ("2020-02-29", 5, -1, "2020-01-29"), + ("2020-02-20", 5, -1, "2020-01-20"), + ("2020-02-29", 8, -1, "2020-02-28"), + ("2021-03-01", 8, -1, "2021-02-28"), + ("1980/12/21", 8, 100, "1981/03/31"), + ("1980/12/21", 8, -100, "1980/09/12"), + ("1980/12/21", 8, 1000, "1983/09/17"), + ("1980/12/21", 8, -1000, "1978/03/27"), + ]; + + for (original, cursor, amount, expected) in tests { + let rope = Rope::from_str(original); + let range = Range::new(cursor, cursor + 1); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range) + .unwrap() + .increment(amount) + .1, + expected.into() + ); + } + } +} diff --git a/helix-core/src/increment/mod.rs b/helix-core/src/increment/mod.rs new file mode 100644 index 00000000..71a1f183 --- /dev/null +++ b/helix-core/src/increment/mod.rs @@ -0,0 +1,8 @@ +pub mod date; +pub mod number; + +use crate::{Range, Tendril}; + +pub trait Increment { + fn increment(&self, amount: i64) -> (Range, Tendril); +} diff --git a/helix-core/src/increment/number.rs b/helix-core/src/increment/number.rs new file mode 100644 index 00000000..a19b7e75 --- /dev/null +++ b/helix-core/src/increment/number.rs @@ -0,0 +1,507 @@ +use std::borrow::Cow; + +use ropey::RopeSlice; + +use super::Increment; + +use crate::{ + textobject::{textobject_word, TextObject}, + Range, Tendril, +}; + +#[derive(Debug, PartialEq, Eq)] +pub struct NumberIncrementor<'a> { + value: i64, + radix: u32, + range: Range, + + text: RopeSlice<'a>, +} + +impl<'a> NumberIncrementor<'a> { + /// Return information about number under rang if there is one. + pub fn from_range(text: RopeSlice, range: Range) -> Option { + // If the cursor is on the minus sign of a number we want to get the word textobject to the + // right of it. + let range = if range.to() < text.len_chars() + && range.to() - range.from() <= 1 + && text.char(range.from()) == '-' + { + Range::new(range.from() + 1, range.to() + 1) + } else { + range + }; + + let range = textobject_word(text, range, TextObject::Inside, 1, false); + + // If there is a minus sign to the left of the word object, we want to include it in the range. + let range = if range.from() > 0 && text.char(range.from() - 1) == '-' { + range.extend(range.from() - 1, range.from()) + } else { + range + }; + + let word: String = text + .slice(range.from()..range.to()) + .chars() + .filter(|&c| c != '_') + .collect(); + let (radix, prefixed) = if word.starts_with("0x") { + (16, true) + } else if word.starts_with("0o") { + (8, true) + } else if word.starts_with("0b") { + (2, true) + } else { + (10, false) + }; + + let number = if prefixed { &word[2..] } else { &word }; + + let value = i128::from_str_radix(number, radix).ok()?; + if (value.is_positive() && value.leading_zeros() < 64) + || (value.is_negative() && value.leading_ones() < 64) + { + return None; + } + + let value = value as i64; + Some(NumberIncrementor { + range, + value, + radix, + text, + }) + } +} + +impl<'a> Increment for NumberIncrementor<'a> { + fn increment(&self, amount: i64) -> (Range, Tendril) { + let old_text: Cow = self.text.slice(self.range.from()..self.range.to()).into(); + let old_length = old_text.len(); + let new_value = self.value.wrapping_add(amount); + + // Get separator indexes from right to left. + let separator_rtl_indexes: Vec = old_text + .chars() + .rev() + .enumerate() + .filter_map(|(i, c)| if c == '_' { Some(i) } else { None }) + .collect(); + + let format_length = if self.radix == 10 { + match (self.value.is_negative(), new_value.is_negative()) { + (true, false) => old_length - 1, + (false, true) => old_length + 1, + _ => old_text.len(), + } + } else { + old_text.len() - 2 + } - separator_rtl_indexes.len(); + + let mut new_text = match self.radix { + 2 => format!("0b{:01$b}", new_value, format_length), + 8 => format!("0o{:01$o}", new_value, format_length), + 10 if old_text.starts_with('0') || old_text.starts_with("-0") => { + format!("{:01$}", new_value, format_length) + } + 10 => format!("{}", new_value), + 16 => { + let (lower_count, upper_count): (usize, usize) = + old_text.chars().skip(2).fold((0, 0), |(lower, upper), c| { + ( + lower + c.is_ascii_lowercase().then(|| 1).unwrap_or(0), + upper + c.is_ascii_uppercase().then(|| 1).unwrap_or(0), + ) + }); + if upper_count > lower_count { + format!("0x{:01$X}", new_value, format_length) + } else { + format!("0x{:01$x}", new_value, format_length) + } + } + _ => unimplemented!("radix not supported: {}", self.radix), + }; + + // Add separators from original number. + for &rtl_index in &separator_rtl_indexes { + if rtl_index < new_text.len() { + let new_index = new_text.len() - rtl_index; + new_text.insert(new_index, '_'); + } + } + + // Add in additional separators if necessary. + if new_text.len() > old_length && !separator_rtl_indexes.is_empty() { + let spacing = match separator_rtl_indexes.as_slice() { + [.., b, a] => a - b - 1, + _ => separator_rtl_indexes[0], + }; + + let prefix_length = if self.radix == 10 { 0 } else { 2 }; + if let Some(mut index) = new_text.find('_') { + while index - prefix_length > spacing { + index -= spacing; + new_text.insert(index, '_'); + } + } + } + + (self.range, new_text.into()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::Rope; + + #[test] + fn test_decimal_at_point() { + let rope = Rope::from_str("Test text 12345 more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 15), + value: 12345, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_uppercase_hexadecimal_at_point() { + let rope = Rope::from_str("Test text 0x123ABCDEF more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 21), + value: 0x123ABCDEF, + radix: 16, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_lowercase_hexadecimal_at_point() { + let rope = Rope::from_str("Test text 0xfa3b4e more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 18), + value: 0xfa3b4e, + radix: 16, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_octal_at_point() { + let rope = Rope::from_str("Test text 0o1074312 more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 19), + value: 0o1074312, + radix: 8, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_binary_at_point() { + let rope = Rope::from_str("Test text 0b10111010010101 more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 26), + value: 0b10111010010101, + radix: 2, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_negative_decimal_at_point() { + let rope = Rope::from_str("Test text -54321 more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 16), + value: -54321, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_decimal_with_leading_zeroes_at_point() { + let rope = Rope::from_str("Test text 000045326 more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 19), + value: 45326, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_negative_decimal_cursor_on_minus_sign() { + let rope = Rope::from_str("Test text -54321 more text."); + let range = Range::point(10); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 16), + value: -54321, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_number_under_range_start_of_rope() { + let rope = Rope::from_str("100"); + let range = Range::point(0); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(0, 3), + value: 100, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_number_under_range_end_of_rope() { + let rope = Rope::from_str("100"); + let range = Range::point(2); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(0, 3), + value: 100, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_number_surrounded_by_punctuation() { + let rope = Rope::from_str(",100;"); + let range = Range::point(1); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(1, 4), + value: 100, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_not_a_number_point() { + let rope = Rope::from_str("Test text 45326 more text."); + let range = Range::point(6); + assert_eq!(NumberIncrementor::from_range(rope.slice(..), range), None); + } + + #[test] + fn test_number_too_large_at_point() { + let rope = Rope::from_str("Test text 0xFFFFFFFFFFFFFFFFF more text."); + let range = Range::point(12); + assert_eq!(NumberIncrementor::from_range(rope.slice(..), range), None); + } + + #[test] + fn test_number_cursor_one_right_of_number() { + let rope = Rope::from_str("100 "); + let range = Range::point(3); + assert_eq!(NumberIncrementor::from_range(rope.slice(..), range), None); + } + + #[test] + fn test_number_cursor_one_left_of_number() { + let rope = Rope::from_str(" 100"); + let range = Range::point(0); + assert_eq!(NumberIncrementor::from_range(rope.slice(..), range), None); + } + + #[test] + fn test_increment_basic_decimal_numbers() { + let tests = [ + ("100", 1, "101"), + ("100", -1, "99"), + ("99", 1, "100"), + ("100", 1000, "1100"), + ("100", -1000, "-900"), + ("-1", 1, "0"), + ("-1", 2, "1"), + ("1", -1, "0"), + ("1", -2, "-1"), + ]; + + for (original, amount, expected) in tests { + let rope = Rope::from_str(original); + let range = Range::point(0); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range) + .unwrap() + .increment(amount) + .1, + expected.into() + ); + } + } + + #[test] + fn test_increment_basic_hexadedimal_numbers() { + let tests = [ + ("0x0100", 1, "0x0101"), + ("0x0100", -1, "0x00ff"), + ("0x0001", -1, "0x0000"), + ("0x0000", -1, "0xffffffffffffffff"), + ("0xffffffffffffffff", 1, "0x0000000000000000"), + ("0xffffffffffffffff", 2, "0x0000000000000001"), + ("0xffffffffffffffff", -1, "0xfffffffffffffffe"), + ("0xABCDEF1234567890", 1, "0xABCDEF1234567891"), + ("0xabcdef1234567890", 1, "0xabcdef1234567891"), + ]; + + for (original, amount, expected) in tests { + let rope = Rope::from_str(original); + let range = Range::point(0); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range) + .unwrap() + .increment(amount) + .1, + expected.into() + ); + } + } + + #[test] + fn test_increment_basic_octal_numbers() { + let tests = [ + ("0o0107", 1, "0o0110"), + ("0o0110", -1, "0o0107"), + ("0o0001", -1, "0o0000"), + ("0o7777", 1, "0o10000"), + ("0o1000", -1, "0o0777"), + ("0o0107", 10, "0o0121"), + ("0o0000", -1, "0o1777777777777777777777"), + ("0o1777777777777777777777", 1, "0o0000000000000000000000"), + ("0o1777777777777777777777", 2, "0o0000000000000000000001"), + ("0o1777777777777777777777", -1, "0o1777777777777777777776"), + ]; + + for (original, amount, expected) in tests { + let rope = Rope::from_str(original); + let range = Range::point(0); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range) + .unwrap() + .increment(amount) + .1, + expected.into() + ); + } + } + + #[test] + fn test_increment_basic_binary_numbers() { + let tests = [ + ("0b00000100", 1, "0b00000101"), + ("0b00000100", -1, "0b00000011"), + ("0b00000100", 2, "0b00000110"), + ("0b00000100", -2, "0b00000010"), + ("0b00000001", -1, "0b00000000"), + ("0b00111111", 10, "0b01001001"), + ("0b11111111", 1, "0b100000000"), + ("0b10000000", -1, "0b01111111"), + ( + "0b0000", + -1, + "0b1111111111111111111111111111111111111111111111111111111111111111", + ), + ( + "0b1111111111111111111111111111111111111111111111111111111111111111", + 1, + "0b0000000000000000000000000000000000000000000000000000000000000000", + ), + ( + "0b1111111111111111111111111111111111111111111111111111111111111111", + 2, + "0b0000000000000000000000000000000000000000000000000000000000000001", + ), + ( + "0b1111111111111111111111111111111111111111111111111111111111111111", + -1, + "0b1111111111111111111111111111111111111111111111111111111111111110", + ), + ]; + + for (original, amount, expected) in tests { + let rope = Rope::from_str(original); + let range = Range::point(0); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range) + .unwrap() + .increment(amount) + .1, + expected.into() + ); + } + } + + #[test] + fn test_increment_with_separators() { + let tests = [ + ("999_999", 1, "1_000_000"), + ("1_000_000", -1, "999_999"), + ("-999_999", -1, "-1_000_000"), + ("0x0000_0000_0001", 0x1_ffff_0000, "0x0001_ffff_0001"), + ("0x0000_0000_0001", 0x1_ffff_0000, "0x0001_ffff_0001"), + ("0x0000_0000_0001", 0x1_ffff_0000, "0x0001_ffff_0001"), + ("0x0000_0000", -1, "0xffff_ffff_ffff_ffff"), + ("0x0000_0000_0000", -1, "0xffff_ffff_ffff_ffff"), + ("0b01111111_11111111", 1, "0b10000000_00000000"), + ("0b11111111_11111111", 1, "0b1_00000000_00000000"), + ]; + + for (original, amount, expected) in tests { + let rope = Rope::from_str(original); + let range = Range::point(0); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range) + .unwrap() + .increment(amount) + .1, + expected.into() + ); + } + } +} -- cgit v1.2.3-70-g09d2 From 37e484ee38eb5a9b4da280960fb1e29939ee9d39 Mon Sep 17 00:00:00 2001 From: Jason Rodney Hansen Date: Thu, 25 Nov 2021 19:58:23 -0700 Subject: Add support for time and more date formats --- helix-core/src/increment/date.rs | 474 ------------------------------- helix-core/src/increment/date_time.rs | 515 ++++++++++++++++++++++++++++++++++ helix-core/src/increment/mod.rs | 2 +- helix-term/src/commands.rs | 4 +- 4 files changed, 518 insertions(+), 477 deletions(-) delete mode 100644 helix-core/src/increment/date.rs create mode 100644 helix-core/src/increment/date_time.rs (limited to 'helix-core/src/increment') diff --git a/helix-core/src/increment/date.rs b/helix-core/src/increment/date.rs deleted file mode 100644 index 05442990..00000000 --- a/helix-core/src/increment/date.rs +++ /dev/null @@ -1,474 +0,0 @@ -use regex::Regex; - -use std::borrow::Cow; -use std::cmp; - -use ropey::RopeSlice; - -use crate::{Range, Tendril}; - -use chrono::{Datelike, Duration, NaiveDate}; - -use super::Increment; - -fn ndays_in_month(year: i32, month: u32) -> u32 { - // The first day of the next month... - let (y, m) = if month == 12 { - (year + 1, 1) - } else { - (year, month + 1) - }; - let d = NaiveDate::from_ymd(y, m, 1); - - // ...is preceded by the last day of the original month. - d.pred().day() -} - -fn add_days(date: NaiveDate, amount: i64) -> Option { - date.checked_add_signed(Duration::days(amount)) -} - -fn add_months(date: NaiveDate, amount: i64) -> Option { - let month = date.month0() as i64 + amount; - let year = date.year() + i32::try_from(month / 12).ok()?; - let year = if month.is_negative() { year - 1 } else { year }; - - // Normalize month - let month = month % 12; - let month = if month.is_negative() { - month + 13 - } else { - month + 1 - } as u32; - - let day = cmp::min(date.day(), ndays_in_month(year, month)); - - Some(NaiveDate::from_ymd(year, month, day)) -} - -fn add_years(date: NaiveDate, amount: i64) -> Option { - let year = i32::try_from(date.year() as i64 + amount).ok()?; - let ndays = ndays_in_month(year, date.month()); - - if date.day() > ndays { - let d = NaiveDate::from_ymd(year, date.month(), ndays); - Some(d.succ()) - } else { - date.with_year(year) - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -struct Format { - regex: &'static str, - separator: char, -} - -// Only support formats that aren't region specific. -static FORMATS: &[Format] = &[ - Format { - regex: r"(\d{4})-(\d{2})-(\d{2})", - separator: '-', - }, - Format { - regex: r"(\d{4})/(\d{2})/(\d{2})", - separator: '/', - }, -]; - -const DATE_LENGTH: usize = 10; - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -enum DateField { - Year, - Month, - Day, -} - -#[derive(Debug, PartialEq, Eq)] -pub struct DateIncrementor { - date: NaiveDate, - range: Range, - field: DateField, - format: Format, -} - -impl DateIncrementor { - pub fn from_range(text: RopeSlice, range: Range) -> Option { - let range = if range.is_empty() { - if range.anchor < text.len_bytes() { - // Treat empty range as a cursor range. - range.put_cursor(text, range.anchor + 1, true) - } else { - // The range is empty and at the end of the text. - return None; - } - } else { - range - }; - - let from = range.from().saturating_sub(DATE_LENGTH); - let to = (range.from() + DATE_LENGTH).min(text.len_chars()); - - let (from_in_text, to_in_text) = (range.from() - from, range.to() - from); - let text: Cow = text.slice(from..to).into(); - - FORMATS.iter().find_map(|&format| { - let re = Regex::new(format.regex).ok()?; - let captures = re.captures(&text)?; - - let date = captures.get(0)?; - let offset = range.from() - from_in_text; - let range = Range::new(date.start() + offset, date.end() + offset); - - let (year, month, day) = (captures.get(1)?, captures.get(2)?, captures.get(3)?); - let (year_range, month_range, day_range) = (year.range(), month.range(), day.range()); - - let field = if year_range.contains(&from_in_text) - && year_range.contains(&(to_in_text - 1)) - { - DateField::Year - } else if month_range.contains(&from_in_text) && month_range.contains(&(to_in_text - 1)) - { - DateField::Month - } else if day_range.contains(&from_in_text) && day_range.contains(&(to_in_text - 1)) { - DateField::Day - } else { - return None; - }; - - let date = NaiveDate::from_ymd_opt( - year.as_str().parse::().ok()?, - month.as_str().parse::().ok()?, - day.as_str().parse::().ok()?, - )?; - - Some(DateIncrementor { - date, - field, - range, - format, - }) - }) - } -} - -impl Increment for DateIncrementor { - fn increment(&self, amount: i64) -> (Range, Tendril) { - let date = match self.field { - DateField::Year => add_years(self.date, amount), - DateField::Month => add_months(self.date, amount), - DateField::Day => add_days(self.date, amount), - } - .unwrap_or(self.date); - - ( - self.range, - format!( - "{:04}{}{:02}{}{:02}", - date.year(), - self.format.separator, - date.month(), - self.format.separator, - date.day() - ) - .into(), - ) - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::Rope; - - #[test] - fn test_create_incrementor_for_year_with_dashes() { - let rope = Rope::from_str("2021-11-15"); - - for cursor in 0..=3 { - let range = Range::new(cursor, cursor + 1); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(0, 10), - field: DateField::Year, - format: FORMATS[0], - }) - ); - } - } - - #[test] - fn test_create_incrementor_for_month_with_dashes() { - let rope = Rope::from_str("2021-11-15"); - - for cursor in 5..=6 { - let range = Range::new(cursor, cursor + 1); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(0, 10), - field: DateField::Month, - format: FORMATS[0], - }) - ); - } - } - - #[test] - fn test_create_incrementor_for_day_with_dashes() { - let rope = Rope::from_str("2021-11-15"); - - for cursor in 8..=9 { - let range = Range::new(cursor, cursor + 1); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(0, 10), - field: DateField::Day, - format: FORMATS[0], - }) - ); - } - } - - #[test] - fn test_try_create_incrementor_on_dashes() { - let rope = Rope::from_str("2021-11-15"); - - for &cursor in &[4, 7] { - let range = Range::new(cursor, cursor + 1); - assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None,); - } - } - - #[test] - fn test_create_incrementor_for_year_with_slashes() { - let rope = Rope::from_str("2021/11/15"); - - for cursor in 0..=3 { - let range = Range::new(cursor, cursor + 1); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(0, 10), - field: DateField::Year, - format: FORMATS[1], - }) - ); - } - } - - #[test] - fn test_create_incrementor_for_month_with_slashes() { - let rope = Rope::from_str("2021/11/15"); - - for cursor in 5..=6 { - let range = Range::new(cursor, cursor + 1); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(0, 10), - field: DateField::Month, - format: FORMATS[1], - }) - ); - } - } - - #[test] - fn test_create_incrementor_for_day_with_slashes() { - let rope = Rope::from_str("2021/11/15"); - - for cursor in 8..=9 { - let range = Range::new(cursor, cursor + 1); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(0, 10), - field: DateField::Day, - format: FORMATS[1], - }) - ); - } - } - - #[test] - fn test_try_create_incrementor_on_slashes() { - let rope = Rope::from_str("2021/11/15"); - - for &cursor in &[4, 7] { - let range = Range::new(cursor, cursor + 1); - assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None,); - } - } - - #[test] - fn test_date_surrounded_by_spaces() { - let rope = Rope::from_str(" 2021-11-15 "); - let range = Range::new(3, 4); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(3, 13), - field: DateField::Year, - format: FORMATS[0], - }) - ); - } - - #[test] - fn test_date_in_single_quotes() { - let rope = Rope::from_str("date = '2021-11-15'"); - let range = Range::new(10, 11); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(8, 18), - field: DateField::Year, - format: FORMATS[0], - }) - ); - } - - #[test] - fn test_date_in_double_quotes() { - let rope = Rope::from_str("let date = \"2021-11-15\";"); - let range = Range::new(12, 13); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(12, 22), - field: DateField::Year, - format: FORMATS[0], - }) - ); - } - - #[test] - fn test_date_cursor_one_right_of_date() { - let rope = Rope::from_str("2021-11-15 "); - let range = Range::new(10, 11); - assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None); - } - - #[test] - fn test_date_cursor_one_left_of_number() { - let rope = Rope::from_str(" 2021-11-15"); - let range = Range::new(0, 1); - assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None); - } - - #[test] - fn test_date_empty_range_at_beginning() { - let rope = Rope::from_str("2021-11-15"); - let range = Range::point(0); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(0, 10), - field: DateField::Year, - format: FORMATS[0], - }) - ); - } - - #[test] - fn test_date_empty_range_at_in_middle() { - let rope = Rope::from_str("2021-11-15"); - let range = Range::point(5); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(0, 10), - field: DateField::Month, - format: FORMATS[0], - }) - ); - } - - #[test] - fn test_date_empty_range_at_end() { - let rope = Rope::from_str("2021-11-15"); - let range = Range::point(10); - assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None); - } - - #[test] - fn test_invalid_dates() { - let tests = [ - "0000-00-00", - "1980-2-21", - "1980-12-1", - "12345", - "2020-02-30", - "1999-12-32", - "19-12-32", - "1-2-3", - "0000/00/00", - "1980/2/21", - "1980/12/1", - "12345", - "2020/02/30", - "1999/12/32", - "19/12/32", - "1/2/3", - ]; - - for invalid in tests { - let rope = Rope::from_str(invalid); - let range = Range::new(0, 1); - - assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None); - } - } - - #[test] - fn test_increment_dates() { - let tests = [ - // (original, cursor, amount, expected) - ("2020-02-28", 0, 1, "2021-02-28"), - ("2020-02-29", 0, 1, "2021-03-01"), - ("2020-01-31", 5, 1, "2020-02-29"), - ("2020-01-20", 5, 1, "2020-02-20"), - ("2021-01-01", 5, -1, "2020-12-01"), - ("2021-01-31", 5, -2, "2020-11-30"), - ("2020-02-28", 8, 1, "2020-02-29"), - ("2021-02-28", 8, 1, "2021-03-01"), - ("2021-02-28", 0, -1, "2020-02-28"), - ("2021-03-01", 0, -1, "2020-03-01"), - ("2020-02-29", 5, -1, "2020-01-29"), - ("2020-02-20", 5, -1, "2020-01-20"), - ("2020-02-29", 8, -1, "2020-02-28"), - ("2021-03-01", 8, -1, "2021-02-28"), - ("1980/12/21", 8, 100, "1981/03/31"), - ("1980/12/21", 8, -100, "1980/09/12"), - ("1980/12/21", 8, 1000, "1983/09/17"), - ("1980/12/21", 8, -1000, "1978/03/27"), - ]; - - for (original, cursor, amount, expected) in tests { - let rope = Rope::from_str(original); - let range = Range::new(cursor, cursor + 1); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range) - .unwrap() - .increment(amount) - .1, - expected.into() - ); - } - } -} diff --git a/helix-core/src/increment/date_time.rs b/helix-core/src/increment/date_time.rs new file mode 100644 index 00000000..39380104 --- /dev/null +++ b/helix-core/src/increment/date_time.rs @@ -0,0 +1,515 @@ +use chrono::{Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; +use once_cell::sync::Lazy; +use regex::Regex; +use ropey::RopeSlice; + +use std::borrow::Cow; +use std::cmp; + +use super::Increment; +use crate::{Range, Tendril}; + +#[derive(Debug, PartialEq, Eq)] +pub struct DateTimeIncrementor { + date_time: NaiveDateTime, + range: Range, + format: Format, + field: DateField, +} + +impl DateTimeIncrementor { + pub fn from_range(text: RopeSlice, range: Range) -> Option { + let range = if range.is_empty() { + if range.anchor < text.len_chars() { + // Treat empty range as a cursor range. + range.put_cursor(text, range.anchor + 1, true) + } else { + // The range is empty and at the end of the text. + return None; + } + } else { + range + }; + + FORMATS.iter().find_map(|format| { + let from = range.from().saturating_sub(format.max_len); + let to = (range.from() + format.max_len).min(text.len_chars()); + + let (from_in_text, to_in_text) = (range.from() - from, range.to() - from); + let text: Cow = text.slice(from..to).into(); + + let captures = format.regex.captures(&text)?; + if captures.len() - 1 != format.fields.len() { + return None; + } + + let date_time = captures.get(0)?; + let offset = range.from() - from_in_text; + let range = Range::new(date_time.start() + offset, date_time.end() + offset); + + let field = captures + .iter() + .skip(1) + .enumerate() + .find_map(|(i, capture)| { + let capture = capture?; + let capture_range = capture.range(); + + if capture_range.contains(&from_in_text) + && capture_range.contains(&(to_in_text - 1)) + { + Some(format.fields[i]) + } else { + None + } + })?; + + let has_date = format.fields.iter().any(|f| f.unit.is_date()); + let has_time = format.fields.iter().any(|f| f.unit.is_time()); + + let date_time = match (has_date, has_time) { + (true, true) => NaiveDateTime::parse_from_str( + &text[date_time.start()..date_time.end()], + format.fmt, + ) + .ok()?, + (true, false) => { + let date = NaiveDate::parse_from_str( + &text[date_time.start()..date_time.end()], + format.fmt, + ) + .ok()?; + + date.and_hms(0, 0, 0) + } + (false, true) => { + let time = NaiveTime::parse_from_str( + &text[date_time.start()..date_time.end()], + format.fmt, + ) + .ok()?; + + NaiveDate::from_ymd(0, 1, 1).and_time(time) + } + (false, false) => return None, + }; + + Some(DateTimeIncrementor { + date_time, + range, + format: format.clone(), + field, + }) + }) + } +} + +impl Increment for DateTimeIncrementor { + fn increment(&self, amount: i64) -> (Range, Tendril) { + let date_time = match self.field.unit { + DateUnit::Years => add_years(self.date_time, amount), + DateUnit::Months => add_months(self.date_time, amount), + DateUnit::Days => add_duration(self.date_time, Duration::days(amount)), + DateUnit::Hours => add_duration(self.date_time, Duration::hours(amount)), + DateUnit::Minutes => add_duration(self.date_time, Duration::minutes(amount)), + DateUnit::Seconds => add_duration(self.date_time, Duration::seconds(amount)), + DateUnit::AmPm => toggle_am_pm(self.date_time), + } + .unwrap_or(self.date_time); + + ( + self.range, + date_time.format(self.format.fmt).to_string().into(), + ) + } +} + +static FORMATS: Lazy> = Lazy::new(|| { + vec![ + Format::new("%Y-%m-%d %H:%M:%S"), // 2021-11-24 07:12:23 + Format::new("%Y/%m/%d %H:%M:%S"), // 2021/11/24 07:12:23 + Format::new("%Y-%m-%d %H:%M"), // 2021-11-24 07:12 + Format::new("%Y/%m/%d %H:%M"), // 2021/11/24 07:12 + Format::new("%Y-%m-%d"), // 2021-11-24 + Format::new("%Y/%m/%d"), // 2021/11/24 + Format::new("%a %b %d %Y"), // Wed Nov 24 2021 + Format::new("%d-%b-%Y"), // 24-Nov-2021 + Format::new("%Y %b %d"), // 2021 Nov 24 + Format::new("%b %d, %Y"), // Nov 24, 2021 + Format::new("%-I:%M:%S %P"), // 7:21:53 am + Format::new("%-I:%M %P"), // 7:21 am + Format::new("%-I:%M:%S %p"), // 7:21:53 AM + Format::new("%-I:%M %p"), // 7:21 AM + Format::new("%H:%M:%S"), // 23:24:23 + Format::new("%H:%M"), // 23:24 + ] +}); + +#[derive(Clone, Debug)] +struct Format { + fmt: &'static str, + fields: Vec, + regex: Regex, + max_len: usize, +} + +impl Format { + fn new(fmt: &'static str) -> Self { + let mut remaining = fmt; + let mut fields = Vec::new(); + let mut regex = String::new(); + let mut max_len = 0; + + while let Some(i) = remaining.find('%') { + let mut chars = remaining[i + 1..].chars(); + let spec_len = if let Some(c) = chars.next() { + if c == '-' { + if chars.next().is_some() { + 2 + } else { + 0 + } + } else { + 1 + } + } else { + 0 + }; + + if i < remaining.len() - spec_len { + let specifier = &remaining[i + 1..i + 1 + spec_len]; + if let Some(field) = DateField::from_specifier(specifier) { + fields.push(field); + max_len += field.max_len + remaining[..i].len(); + regex += &remaining[..i]; + regex += &format!("({})", field.regex); + remaining = &remaining[i + spec_len + 1..]; + } else { + regex += &remaining[..=i]; + } + } else { + regex += remaining; + } + } + + let regex = Regex::new(®ex).unwrap(); + + Self { + fmt, + fields, + regex, + max_len, + } + } +} + +impl PartialEq for Format { + fn eq(&self, other: &Self) -> bool { + self.fmt == other.fmt && self.fields == other.fields && self.max_len == other.max_len + } +} + +impl Eq for Format {} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +struct DateField { + regex: &'static str, + unit: DateUnit, + max_len: usize, +} + +impl DateField { + fn from_specifier(specifier: &str) -> Option { + match specifier { + "Y" => Some(DateField { + regex: r"\d{4}", + unit: DateUnit::Years, + max_len: 5, + }), + "y" => Some(DateField { + regex: r"\d\d", + unit: DateUnit::Years, + max_len: 2, + }), + "m" => Some(DateField { + regex: r"[0-1]\d", + unit: DateUnit::Months, + max_len: 2, + }), + "d" => Some(DateField { + regex: r"[0-3]\d", + unit: DateUnit::Days, + max_len: 2, + }), + "-d" => Some(DateField { + regex: r"[1-3]?\d", + unit: DateUnit::Days, + max_len: 2, + }), + "a" => Some(DateField { + regex: r"Sun|Mon|Tue|Wed|Thu|Fri|Sat", + unit: DateUnit::Days, + max_len: 3, + }), + "A" => Some(DateField { + regex: r"Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday", + unit: DateUnit::Days, + max_len: 9, + }), + "b" | "h" => Some(DateField { + regex: r"Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec", + unit: DateUnit::Months, + max_len: 3, + }), + "B" => Some(DateField { + regex: r"January|February|March|April|May|June|July|August|September|October|November|December", + unit: DateUnit::Months, + max_len: 9, + }), + "H" => Some(DateField { + regex: r"[0-2]\d", + unit: DateUnit::Hours, + max_len: 2, + }), + "M" => Some(DateField { + regex: r"[0-5]\d", + unit: DateUnit::Minutes, + max_len: 2, + }), + "S" => Some(DateField { + regex: r"[0-5]\d", + unit: DateUnit::Seconds, + max_len: 2, + }), + "I" => Some(DateField { + regex: r"[0-1]\d", + unit: DateUnit::Hours, + max_len: 2, + }), + "-I" => Some(DateField { + regex: r"1?\d", + unit: DateUnit::Hours, + max_len: 2, + }), + "P" => Some(DateField { + regex: r"am|pm", + unit: DateUnit::AmPm, + max_len: 2, + }), + "p" => Some(DateField { + regex: r"AM|PM", + unit: DateUnit::AmPm, + max_len: 2, + }), + _ => None, + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum DateUnit { + Years, + Months, + Days, + Hours, + Minutes, + Seconds, + AmPm, +} + +impl DateUnit { + fn is_date(self) -> bool { + matches!(self, DateUnit::Years | DateUnit::Months | DateUnit::Days) + } + + fn is_time(self) -> bool { + matches!( + self, + DateUnit::Hours | DateUnit::Minutes | DateUnit::Seconds + ) + } +} + +fn ndays_in_month(year: i32, month: u32) -> u32 { + // The first day of the next month... + let (y, m) = if month == 12 { + (year + 1, 1) + } else { + (year, month + 1) + }; + let d = NaiveDate::from_ymd(y, m, 1); + + // ...is preceded by the last day of the original month. + d.pred().day() +} + +fn add_months(date_time: NaiveDateTime, amount: i64) -> Option { + let month = date_time.month0() as i64 + amount; + let year = date_time.year() + i32::try_from(month / 12).ok()?; + let year = if month.is_negative() { year - 1 } else { year }; + + // Normalize month + let month = month % 12; + let month = if month.is_negative() { + month + 13 + } else { + month + 1 + } as u32; + + let day = cmp::min(date_time.day(), ndays_in_month(year, month)); + + Some(NaiveDate::from_ymd(year, month, day).and_time(date_time.time())) +} + +fn add_years(date_time: NaiveDateTime, amount: i64) -> Option { + let year = i32::try_from(date_time.year() as i64 + amount).ok()?; + let ndays = ndays_in_month(year, date_time.month()); + + if date_time.day() > ndays { + let d = NaiveDate::from_ymd(year, date_time.month(), ndays); + Some(d.succ().and_time(date_time.time())) + } else { + date_time.with_year(year) + } +} + +fn add_duration(date_time: NaiveDateTime, duration: Duration) -> Option { + date_time.checked_add_signed(duration) +} + +fn toggle_am_pm(date_time: NaiveDateTime) -> Option { + if date_time.hour() < 12 { + add_duration(date_time, Duration::hours(12)) + } else { + add_duration(date_time, Duration::hours(-12)) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::Rope; + + #[test] + fn test_increment_date_times() { + let tests = [ + // (original, cursor, amount, expected) + ("2020-02-28", 0, 1, "2021-02-28"), + ("2020-02-29", 0, 1, "2021-03-01"), + ("2020-01-31", 5, 1, "2020-02-29"), + ("2020-01-20", 5, 1, "2020-02-20"), + ("2021-01-01", 5, -1, "2020-12-01"), + ("2021-01-31", 5, -2, "2020-11-30"), + ("2020-02-28", 8, 1, "2020-02-29"), + ("2021-02-28", 8, 1, "2021-03-01"), + ("2021-02-28", 0, -1, "2020-02-28"), + ("2021-03-01", 0, -1, "2020-03-01"), + ("2020-02-29", 5, -1, "2020-01-29"), + ("2020-02-20", 5, -1, "2020-01-20"), + ("2020-02-29", 8, -1, "2020-02-28"), + ("2021-03-01", 8, -1, "2021-02-28"), + ("1980/12/21", 8, 100, "1981/03/31"), + ("1980/12/21", 8, -100, "1980/09/12"), + ("1980/12/21", 8, 1000, "1983/09/17"), + ("1980/12/21", 8, -1000, "1978/03/27"), + ("2021-11-24 07:12:23", 0, 1, "2022-11-24 07:12:23"), + ("2021-11-24 07:12:23", 5, 1, "2021-12-24 07:12:23"), + ("2021-11-24 07:12:23", 8, 1, "2021-11-25 07:12:23"), + ("2021-11-24 07:12:23", 11, 1, "2021-11-24 08:12:23"), + ("2021-11-24 07:12:23", 14, 1, "2021-11-24 07:13:23"), + ("2021-11-24 07:12:23", 17, 1, "2021-11-24 07:12:24"), + ("2021/11/24 07:12:23", 0, 1, "2022/11/24 07:12:23"), + ("2021/11/24 07:12:23", 5, 1, "2021/12/24 07:12:23"), + ("2021/11/24 07:12:23", 8, 1, "2021/11/25 07:12:23"), + ("2021/11/24 07:12:23", 11, 1, "2021/11/24 08:12:23"), + ("2021/11/24 07:12:23", 14, 1, "2021/11/24 07:13:23"), + ("2021/11/24 07:12:23", 17, 1, "2021/11/24 07:12:24"), + ("2021-11-24 07:12", 0, 1, "2022-11-24 07:12"), + ("2021-11-24 07:12", 5, 1, "2021-12-24 07:12"), + ("2021-11-24 07:12", 8, 1, "2021-11-25 07:12"), + ("2021-11-24 07:12", 11, 1, "2021-11-24 08:12"), + ("2021-11-24 07:12", 14, 1, "2021-11-24 07:13"), + ("2021/11/24 07:12", 0, 1, "2022/11/24 07:12"), + ("2021/11/24 07:12", 5, 1, "2021/12/24 07:12"), + ("2021/11/24 07:12", 8, 1, "2021/11/25 07:12"), + ("2021/11/24 07:12", 11, 1, "2021/11/24 08:12"), + ("2021/11/24 07:12", 14, 1, "2021/11/24 07:13"), + ("Wed Nov 24 2021", 0, 1, "Thu Nov 25 2021"), + ("Wed Nov 24 2021", 4, 1, "Fri Dec 24 2021"), + ("Wed Nov 24 2021", 8, 1, "Thu Nov 25 2021"), + ("Wed Nov 24 2021", 11, 1, "Thu Nov 24 2022"), + ("24-Nov-2021", 0, 1, "25-Nov-2021"), + ("24-Nov-2021", 3, 1, "24-Dec-2021"), + ("24-Nov-2021", 7, 1, "24-Nov-2022"), + ("2021 Nov 24", 0, 1, "2022 Nov 24"), + ("2021 Nov 24", 5, 1, "2021 Dec 24"), + ("2021 Nov 24", 9, 1, "2021 Nov 25"), + ("Nov 24, 2021", 0, 1, "Dec 24, 2021"), + ("Nov 24, 2021", 4, 1, "Nov 25, 2021"), + ("Nov 24, 2021", 8, 1, "Nov 24, 2022"), + ("7:21:53 am", 0, 1, "8:21:53 am"), + ("7:21:53 am", 3, 1, "7:22:53 am"), + ("7:21:53 am", 5, 1, "7:21:54 am"), + ("7:21:53 am", 8, 1, "7:21:53 pm"), + ("7:21:53 AM", 0, 1, "8:21:53 AM"), + ("7:21:53 AM", 3, 1, "7:22:53 AM"), + ("7:21:53 AM", 5, 1, "7:21:54 AM"), + ("7:21:53 AM", 8, 1, "7:21:53 PM"), + ("7:21 am", 0, 1, "8:21 am"), + ("7:21 am", 3, 1, "7:22 am"), + ("7:21 am", 5, 1, "7:21 pm"), + ("7:21 AM", 0, 1, "8:21 AM"), + ("7:21 AM", 3, 1, "7:22 AM"), + ("7:21 AM", 5, 1, "7:21 PM"), + ("23:24:23", 1, 1, "00:24:23"), + ("23:24:23", 3, 1, "23:25:23"), + ("23:24:23", 6, 1, "23:24:24"), + ("23:24", 1, 1, "00:24"), + ("23:24", 3, 1, "23:25"), + ]; + + for (original, cursor, amount, expected) in tests { + let rope = Rope::from_str(original); + let range = Range::new(cursor, cursor + 1); + assert_eq!( + DateTimeIncrementor::from_range(rope.slice(..), range) + .unwrap() + .increment(amount) + .1, + expected.into() + ); + } + } + + #[test] + fn test_invalid_date_times() { + let tests = [ + "0000-00-00", + "1980-2-21", + "1980-12-1", + "12345", + "2020-02-30", + "1999-12-32", + "19-12-32", + "1-2-3", + "0000/00/00", + "1980/2/21", + "1980/12/1", + "12345", + "2020/02/30", + "1999/12/32", + "19/12/32", + "1/2/3", + "123:456:789", + "11:61", + "2021-55-12 08:12:54", + ]; + + for invalid in tests { + let rope = Rope::from_str(invalid); + let range = Range::new(0, 1); + + assert_eq!(DateTimeIncrementor::from_range(rope.slice(..), range), None) + } + } +} diff --git a/helix-core/src/increment/mod.rs b/helix-core/src/increment/mod.rs index 71a1f183..f5945774 100644 --- a/helix-core/src/increment/mod.rs +++ b/helix-core/src/increment/mod.rs @@ -1,4 +1,4 @@ -pub mod date; +pub mod date_time; pub mod number; use crate::{Range, Tendril}; diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 6329dec7..4869a135 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1,7 +1,7 @@ use helix_core::{ comment, coords_at_pos, find_first_non_whitespace_char, find_root, graphemes, history::UndoKind, - increment::date::DateIncrementor, + increment::date_time::DateTimeIncrementor, increment::{number::NumberIncrementor, Increment}, indent, indent::IndentStyle, @@ -5804,7 +5804,7 @@ fn increment_impl(cx: &mut Context, amount: i64) { let changes = selection.ranges().iter().filter_map(|range| { let incrementor: Option> = if let Some(incrementor) = - DateIncrementor::from_range(text.slice(..), *range) + DateTimeIncrementor::from_range(text.slice(..), *range) { Some(Box::new(incrementor)) } else if let Some(incrementor) = NumberIncrementor::from_range(text.slice(..), *range) { -- cgit v1.2.3-70-g09d2 From febee2dc0c20cab360f75c2088c8f59b13a12e95 Mon Sep 17 00:00:00 2001 From: Jason Rodney Hansen Date: Sun, 28 Nov 2021 09:40:33 -0700 Subject: No need to clone format --- helix-core/src/increment/date_time.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'helix-core/src/increment') diff --git a/helix-core/src/increment/date_time.rs b/helix-core/src/increment/date_time.rs index 39380104..195bc14b 100644 --- a/helix-core/src/increment/date_time.rs +++ b/helix-core/src/increment/date_time.rs @@ -13,7 +13,7 @@ use crate::{Range, Tendril}; pub struct DateTimeIncrementor { date_time: NaiveDateTime, range: Range, - format: Format, + fmt: &'static str, field: DateField, } @@ -97,7 +97,7 @@ impl DateTimeIncrementor { Some(DateTimeIncrementor { date_time, range, - format: format.clone(), + fmt: format.fmt, field, }) }) @@ -117,10 +117,7 @@ impl Increment for DateTimeIncrementor { } .unwrap_or(self.date_time); - ( - self.range, - date_time.format(self.format.fmt).to_string().into(), - ) + (self.range, date_time.format(self.fmt).to_string().into()) } } -- cgit v1.2.3-70-g09d2 From c74cd48f38863b3004eaf2b52336ed0597337044 Mon Sep 17 00:00:00 2001 From: Jason Rodney Hansen Date: Sun, 28 Nov 2021 10:58:52 -0700 Subject: Cleanup --- helix-core/src/increment/date_time.rs | 104 +++++++++++++++++----------------- helix-term/src/commands.rs | 10 ++-- 2 files changed, 57 insertions(+), 57 deletions(-) (limited to 'helix-core/src/increment') diff --git a/helix-core/src/increment/date_time.rs b/helix-core/src/increment/date_time.rs index 195bc14b..39646a17 100644 --- a/helix-core/src/increment/date_time.rs +++ b/helix-core/src/increment/date_time.rs @@ -67,27 +67,16 @@ impl DateTimeIncrementor { let has_date = format.fields.iter().any(|f| f.unit.is_date()); let has_time = format.fields.iter().any(|f| f.unit.is_time()); + let date_time = &text[date_time.start()..date_time.end()]; let date_time = match (has_date, has_time) { - (true, true) => NaiveDateTime::parse_from_str( - &text[date_time.start()..date_time.end()], - format.fmt, - ) - .ok()?, + (true, true) => NaiveDateTime::parse_from_str(date_time, format.fmt).ok()?, (true, false) => { - let date = NaiveDate::parse_from_str( - &text[date_time.start()..date_time.end()], - format.fmt, - ) - .ok()?; + let date = NaiveDate::parse_from_str(date_time, format.fmt).ok()?; date.and_hms(0, 0, 0) } (false, true) => { - let time = NaiveTime::parse_from_str( - &text[date_time.start()..date_time.end()], - format.fmt, - ) - .ok()?; + let time = NaiveTime::parse_from_str(date_time, format.fmt).ok()?; NaiveDate::from_ymd(0, 1, 1).and_time(time) } @@ -123,22 +112,22 @@ impl Increment for DateTimeIncrementor { static FORMATS: Lazy> = Lazy::new(|| { vec![ - Format::new("%Y-%m-%d %H:%M:%S"), // 2021-11-24 07:12:23 - Format::new("%Y/%m/%d %H:%M:%S"), // 2021/11/24 07:12:23 - Format::new("%Y-%m-%d %H:%M"), // 2021-11-24 07:12 - Format::new("%Y/%m/%d %H:%M"), // 2021/11/24 07:12 - Format::new("%Y-%m-%d"), // 2021-11-24 - Format::new("%Y/%m/%d"), // 2021/11/24 - Format::new("%a %b %d %Y"), // Wed Nov 24 2021 - Format::new("%d-%b-%Y"), // 24-Nov-2021 - Format::new("%Y %b %d"), // 2021 Nov 24 - Format::new("%b %d, %Y"), // Nov 24, 2021 - Format::new("%-I:%M:%S %P"), // 7:21:53 am - Format::new("%-I:%M %P"), // 7:21 am - Format::new("%-I:%M:%S %p"), // 7:21:53 AM - Format::new("%-I:%M %p"), // 7:21 AM - Format::new("%H:%M:%S"), // 23:24:23 - Format::new("%H:%M"), // 23:24 + Format::new("%Y-%m-%d %H:%M:%S").unwrap(), // 2021-11-24 07:12:23 + Format::new("%Y/%m/%d %H:%M:%S").unwrap(), // 2021/11/24 07:12:23 + Format::new("%Y-%m-%d %H:%M").unwrap(), // 2021-11-24 07:12 + Format::new("%Y/%m/%d %H:%M").unwrap(), // 2021/11/24 07:12 + Format::new("%Y-%m-%d").unwrap(), // 2021-11-24 + Format::new("%Y/%m/%d").unwrap(), // 2021/11/24 + Format::new("%a %b %d %Y").unwrap(), // Wed Nov 24 2021 + Format::new("%d-%b-%Y").unwrap(), // 24-Nov-2021 + Format::new("%Y %b %d").unwrap(), // 2021 Nov 24 + Format::new("%b %d, %Y").unwrap(), // Nov 24, 2021 + Format::new("%-I:%M:%S %P").unwrap(), // 7:21:53 am + Format::new("%-I:%M %P").unwrap(), // 7:21 am + Format::new("%-I:%M:%S %p").unwrap(), // 7:21:53 AM + Format::new("%-I:%M %p").unwrap(), // 7:21 AM + Format::new("%H:%M:%S").unwrap(), // 23:24:23 + Format::new("%H:%M").unwrap(), // 23:24 ] }); @@ -151,55 +140,65 @@ struct Format { } impl Format { - fn new(fmt: &'static str) -> Self { + fn new(fmt: &'static str) -> Result { let mut remaining = fmt; let mut fields = Vec::new(); let mut regex = String::new(); let mut max_len = 0; while let Some(i) = remaining.find('%') { - let mut chars = remaining[i + 1..].chars(); - let spec_len = if let Some(c) = chars.next() { - if c == '-' { - if chars.next().is_some() { - 2 - } else { - 0 - } + let after = &remaining[i + 1..]; + let mut chars = after.chars(); + let c = chars + .next() + .ok_or(FormatError::UnexpectedEndOfFormatString)?; + + let spec_len = if c == '-' { + if let Some(c) = chars.next() { + 1 + c.len_utf8() } else { - 1 + return Err(FormatError::UnexpectedEndOfFormatString); } } else { - 0 + c.len_utf8() }; if i < remaining.len() - spec_len { - let specifier = &remaining[i + 1..i + 1 + spec_len]; + let specifier = &after[..spec_len]; if let Some(field) = DateField::from_specifier(specifier) { fields.push(field); max_len += field.max_len + remaining[..i].len(); regex += &remaining[..i]; regex += &format!("({})", field.regex); - remaining = &remaining[i + spec_len + 1..]; + remaining = &after[spec_len..]; } else { - regex += &remaining[..=i]; + return Err(FormatError::UnsupportedSpecifier( + &remaining[i..i + 1 + spec_len], + )); } } else { - regex += remaining; + return Err(FormatError::UnexpectedEndOfFormatString); } } - let regex = Regex::new(®ex).unwrap(); + let regex = Regex::new(®ex).map_err(FormatError::Regex)?; - Self { + Ok(Self { fmt, fields, regex, max_len, - } + }) } } +#[derive(Clone, Debug)] +enum FormatError { + UnexpectedEndOfFormatString, + UnsupportedSpecifier(&'static str), + Regex(regex::Error), +} + impl PartialEq for Format { fn eq(&self, other: &Self) -> bool { self.fmt == other.fmt && self.fields == other.fields && self.max_len == other.max_len @@ -348,10 +347,11 @@ fn add_months(date_time: NaiveDateTime, amount: i64) -> Option { // Normalize month let month = month % 12; let month = if month.is_negative() { - month + 13 + month + 12 } else { - month + 1 - } as u32; + month + } as u32 + + 1; let day = cmp::min(date_time.day(), ndays_in_month(year, month)); diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 4869a135..c89c81db 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -5803,17 +5803,17 @@ fn increment_impl(cx: &mut Context, amount: i64) { let text = doc.text(); let changes = selection.ranges().iter().filter_map(|range| { - let incrementor: Option> = if let Some(incrementor) = + let incrementor: Box = if let Some(incrementor) = DateTimeIncrementor::from_range(text.slice(..), *range) { - Some(Box::new(incrementor)) + Box::new(incrementor) } else if let Some(incrementor) = NumberIncrementor::from_range(text.slice(..), *range) { - Some(Box::new(incrementor)) + Box::new(incrementor) } else { - None + return None; }; - let (range, new_text) = incrementor?.increment(amount); + let (range, new_text) = incrementor.increment(amount); Some((range.from(), range.to(), Some(new_text))) }); -- cgit v1.2.3-70-g09d2 From 584a31cd9047ce8f93b9eab9233c1b62a9278054 Mon Sep 17 00:00:00 2001 From: Jason Rodney Hansen Date: Mon, 29 Nov 2021 18:19:51 -0700 Subject: Used checked_add for years and months --- helix-core/src/increment/date_time.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'helix-core/src/increment') diff --git a/helix-core/src/increment/date_time.rs b/helix-core/src/increment/date_time.rs index 39646a17..e4227386 100644 --- a/helix-core/src/increment/date_time.rs +++ b/helix-core/src/increment/date_time.rs @@ -340,7 +340,7 @@ fn ndays_in_month(year: i32, month: u32) -> u32 { } fn add_months(date_time: NaiveDateTime, amount: i64) -> Option { - let month = date_time.month0() as i64 + amount; + let month = (date_time.month0() as i64).checked_add(amount)?; let year = date_time.year() + i32::try_from(month / 12).ok()?; let year = if month.is_negative() { year - 1 } else { year }; @@ -359,7 +359,7 @@ fn add_months(date_time: NaiveDateTime, amount: i64) -> Option { } fn add_years(date_time: NaiveDateTime, amount: i64) -> Option { - let year = i32::try_from(date_time.year() as i64 + amount).ok()?; + let year = i32::try_from((date_time.year() as i64).checked_add(amount)?).ok()?; let ndays = ndays_in_month(year, date_time.month()); if date_time.day() > ndays { -- cgit v1.2.3-70-g09d2 From 0b7911d9217f63b0e110923fcddd4534e4d9da38 Mon Sep 17 00:00:00 2001 From: Jason Rodney Hansen Date: Sat, 4 Dec 2021 11:30:05 -0700 Subject: Remove `FormatError` --- helix-core/src/increment/date_time.rs | 80 +++++++++++++---------------------- 1 file changed, 29 insertions(+), 51 deletions(-) (limited to 'helix-core/src/increment') diff --git a/helix-core/src/increment/date_time.rs b/helix-core/src/increment/date_time.rs index e4227386..80f2bf53 100644 --- a/helix-core/src/increment/date_time.rs +++ b/helix-core/src/increment/date_time.rs @@ -112,22 +112,22 @@ impl Increment for DateTimeIncrementor { static FORMATS: Lazy> = Lazy::new(|| { vec![ - Format::new("%Y-%m-%d %H:%M:%S").unwrap(), // 2021-11-24 07:12:23 - Format::new("%Y/%m/%d %H:%M:%S").unwrap(), // 2021/11/24 07:12:23 - Format::new("%Y-%m-%d %H:%M").unwrap(), // 2021-11-24 07:12 - Format::new("%Y/%m/%d %H:%M").unwrap(), // 2021/11/24 07:12 - Format::new("%Y-%m-%d").unwrap(), // 2021-11-24 - Format::new("%Y/%m/%d").unwrap(), // 2021/11/24 - Format::new("%a %b %d %Y").unwrap(), // Wed Nov 24 2021 - Format::new("%d-%b-%Y").unwrap(), // 24-Nov-2021 - Format::new("%Y %b %d").unwrap(), // 2021 Nov 24 - Format::new("%b %d, %Y").unwrap(), // Nov 24, 2021 - Format::new("%-I:%M:%S %P").unwrap(), // 7:21:53 am - Format::new("%-I:%M %P").unwrap(), // 7:21 am - Format::new("%-I:%M:%S %p").unwrap(), // 7:21:53 AM - Format::new("%-I:%M %p").unwrap(), // 7:21 AM - Format::new("%H:%M:%S").unwrap(), // 23:24:23 - Format::new("%H:%M").unwrap(), // 23:24 + Format::new("%Y-%m-%d %H:%M:%S"), // 2021-11-24 07:12:23 + Format::new("%Y/%m/%d %H:%M:%S"), // 2021/11/24 07:12:23 + Format::new("%Y-%m-%d %H:%M"), // 2021-11-24 07:12 + Format::new("%Y/%m/%d %H:%M"), // 2021/11/24 07:12 + Format::new("%Y-%m-%d"), // 2021-11-24 + Format::new("%Y/%m/%d"), // 2021/11/24 + Format::new("%a %b %d %Y"), // Wed Nov 24 2021 + Format::new("%d-%b-%Y"), // 24-Nov-2021 + Format::new("%Y %b %d"), // 2021 Nov 24 + Format::new("%b %d, %Y"), // Nov 24, 2021 + Format::new("%-I:%M:%S %P"), // 7:21:53 am + Format::new("%-I:%M %P"), // 7:21 am + Format::new("%-I:%M:%S %p"), // 7:21:53 AM + Format::new("%-I:%M %p"), // 7:21 AM + Format::new("%H:%M:%S"), // 23:24:23 + Format::new("%H:%M"), // 23:24 ] }); @@ -140,7 +140,7 @@ struct Format { } impl Format { - fn new(fmt: &'static str) -> Result { + fn new(fmt: &'static str) -> Self { let mut remaining = fmt; let mut fields = Vec::new(); let mut regex = String::new(); @@ -149,56 +149,34 @@ impl Format { while let Some(i) = remaining.find('%') { let after = &remaining[i + 1..]; let mut chars = after.chars(); - let c = chars - .next() - .ok_or(FormatError::UnexpectedEndOfFormatString)?; + let c = chars.next().unwrap(); let spec_len = if c == '-' { - if let Some(c) = chars.next() { - 1 + c.len_utf8() - } else { - return Err(FormatError::UnexpectedEndOfFormatString); - } + 1 + chars.next().unwrap().len_utf8() } else { c.len_utf8() }; - if i < remaining.len() - spec_len { - let specifier = &after[..spec_len]; - if let Some(field) = DateField::from_specifier(specifier) { - fields.push(field); - max_len += field.max_len + remaining[..i].len(); - regex += &remaining[..i]; - regex += &format!("({})", field.regex); - remaining = &after[spec_len..]; - } else { - return Err(FormatError::UnsupportedSpecifier( - &remaining[i..i + 1 + spec_len], - )); - } - } else { - return Err(FormatError::UnexpectedEndOfFormatString); - } + let specifier = &after[..spec_len]; + let field = DateField::from_specifier(specifier).unwrap(); + fields.push(field); + max_len += field.max_len + remaining[..i].len(); + regex += &remaining[..i]; + regex += &format!("({})", field.regex); + remaining = &after[spec_len..]; } - let regex = Regex::new(®ex).map_err(FormatError::Regex)?; + let regex = Regex::new(®ex).unwrap(); - Ok(Self { + Self { fmt, fields, regex, max_len, - }) + } } } -#[derive(Clone, Debug)] -enum FormatError { - UnexpectedEndOfFormatString, - UnsupportedSpecifier(&'static str), - Regex(regex::Error), -} - impl PartialEq for Format { fn eq(&self, other: &Self) -> bool { self.fmt == other.fmt && self.fields == other.fields && self.max_len == other.max_len -- cgit v1.2.3-70-g09d2 From 539c27e3f52a7c2744db7e92a45d7185eeeba917 Mon Sep 17 00:00:00 2001 From: Jason Rodney Hansen Date: Sat, 4 Dec 2021 11:31:21 -0700 Subject: Remove `Clone` derive --- helix-core/src/increment/date_time.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'helix-core/src/increment') diff --git a/helix-core/src/increment/date_time.rs b/helix-core/src/increment/date_time.rs index 80f2bf53..e3cfe107 100644 --- a/helix-core/src/increment/date_time.rs +++ b/helix-core/src/increment/date_time.rs @@ -131,7 +131,7 @@ static FORMATS: Lazy> = Lazy::new(|| { ] }); -#[derive(Clone, Debug)] +#[derive(Debug)] struct Format { fmt: &'static str, fields: Vec, -- cgit v1.2.3-70-g09d2 From e7eab95b943ff15396c5d512a9c95650ab98a902 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Fri, 14 Jan 2022 12:25:59 +0900 Subject: Update to rust 1.58, fix a bunch of optional lints --- flake.lock | 24 ++++++++++++------------ helix-core/src/increment/date_time.rs | 32 ++++++++++++++++---------------- helix-term/src/commands.rs | 35 ++++++++++++++++------------------- helix-term/src/job.rs | 14 +++++++------- helix-term/src/keymap.rs | 8 ++++---- helix-term/src/ui/completion.rs | 2 +- helix-tui/src/widgets/block.rs | 10 +++++----- helix-tui/src/widgets/paragraph.rs | 2 +- helix-view/src/info.rs | 4 ++-- 9 files changed, 64 insertions(+), 67 deletions(-) (limited to 'helix-core/src/increment') diff --git a/flake.lock b/flake.lock index 606a72f3..94e443e3 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "devshell": { "locked": { - "lastModified": 1640301433, - "narHash": "sha256-eplae8ZNiEmxbOgwUn9IihaJfEUxoUilkwciRPGskYE=", + "lastModified": 1641980203, + "narHash": "sha256-RiWJ3+6V267Ji+P54K1Xrj1Nsah9BfG/aLfIhqgVyBY=", "owner": "numtide", "repo": "devshell", - "rev": "f87fb932740abe1c1b46f6edd8a36ade17881666", + "rev": "d897c1ddb4eab66cc2b783c7868d78555b9880ad", "type": "github" }, "original": { @@ -41,11 +41,11 @@ ] }, "locked": { - "lastModified": 1641449444, - "narHash": "sha256-InqsyCVafPqXmK7YqUfFVpb6eVYJWUWbYXEvey0J+3c=", + "lastModified": 1642054253, + "narHash": "sha256-kHh9VmaB7gbS6pheheC4x0uT84LEmhfbsbWEQJgU2E4=", "owner": "yusdacra", "repo": "nix-cargo-integration", - "rev": "d1aa15a832db331a97082b7f8c7da737a6789c77", + "rev": "f8fa9af990195a3f63fe2dde84aa187e193da793", "type": "github" }, "original": { @@ -56,11 +56,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1639699734, - "narHash": "sha256-tlX6WebGmiHb2Hmniff+ltYp+7dRfdsBxw9YczLsP60=", + "lastModified": 1641887635, + "narHash": "sha256-kDGpufwzVaiGe5e1sBUBPo9f1YN+nYHJlYqCaVpZTQQ=", "owner": "nixos", "repo": "nixpkgs", - "rev": "03ec468b14067729a285c2c7cfa7b9434a04816c", + "rev": "b2737d4980a17cc2b7d600d7d0b32fd7333aca88", "type": "github" }, "original": { @@ -99,11 +99,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1639880499, - "narHash": "sha256-/BibDmFwgWuuTUkNVO6YlvuTSWM9dpBvlZoTAPs7ORI=", + "lastModified": 1642128126, + "narHash": "sha256-av8JUACdrTfQYl/ftZJvKpZEmZfa0avCq7tt5Usdoq0=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "c6c83589ae048af20d93d01eb07a4176012093d0", + "rev": "ce4ef6f2d74f2b68f7547df1de22d1b0037ce4ad", "type": "github" }, "original": { diff --git a/helix-core/src/increment/date_time.rs b/helix-core/src/increment/date_time.rs index e3cfe107..1703c3ba 100644 --- a/helix-core/src/increment/date_time.rs +++ b/helix-core/src/increment/date_time.rs @@ -195,82 +195,82 @@ struct DateField { impl DateField { fn from_specifier(specifier: &str) -> Option { match specifier { - "Y" => Some(DateField { + "Y" => Some(Self { regex: r"\d{4}", unit: DateUnit::Years, max_len: 5, }), - "y" => Some(DateField { + "y" => Some(Self { regex: r"\d\d", unit: DateUnit::Years, max_len: 2, }), - "m" => Some(DateField { + "m" => Some(Self { regex: r"[0-1]\d", unit: DateUnit::Months, max_len: 2, }), - "d" => Some(DateField { + "d" => Some(Self { regex: r"[0-3]\d", unit: DateUnit::Days, max_len: 2, }), - "-d" => Some(DateField { + "-d" => Some(Self { regex: r"[1-3]?\d", unit: DateUnit::Days, max_len: 2, }), - "a" => Some(DateField { + "a" => Some(Self { regex: r"Sun|Mon|Tue|Wed|Thu|Fri|Sat", unit: DateUnit::Days, max_len: 3, }), - "A" => Some(DateField { + "A" => Some(Self { regex: r"Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday", unit: DateUnit::Days, max_len: 9, }), - "b" | "h" => Some(DateField { + "b" | "h" => Some(Self { regex: r"Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec", unit: DateUnit::Months, max_len: 3, }), - "B" => Some(DateField { + "B" => Some(Self { regex: r"January|February|March|April|May|June|July|August|September|October|November|December", unit: DateUnit::Months, max_len: 9, }), - "H" => Some(DateField { + "H" => Some(Self { regex: r"[0-2]\d", unit: DateUnit::Hours, max_len: 2, }), - "M" => Some(DateField { + "M" => Some(Self { regex: r"[0-5]\d", unit: DateUnit::Minutes, max_len: 2, }), - "S" => Some(DateField { + "S" => Some(Self { regex: r"[0-5]\d", unit: DateUnit::Seconds, max_len: 2, }), - "I" => Some(DateField { + "I" => Some(Self { regex: r"[0-1]\d", unit: DateUnit::Hours, max_len: 2, }), - "-I" => Some(DateField { + "-I" => Some(Self { regex: r"1?\d", unit: DateUnit::Hours, max_len: 2, }), - "P" => Some(DateField { + "P" => Some(Self { regex: r"am|pm", unit: DateUnit::AmPm, max_len: 2, }), - "p" => Some(DateField { + "p" => Some(Self { regex: r"AM|PM", unit: DateUnit::AmPm, max_len: 2, diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index c14216c0..9ac12931 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -173,7 +173,7 @@ macro_rules! static_commands { impl MappableCommand { pub fn execute(&self, cx: &mut Context) { match &self { - MappableCommand::Typable { name, args, doc: _ } => { + Self::Typable { name, args, doc: _ } => { let args: Vec> = args.iter().map(Cow::from).collect(); if let Some(command) = cmd::TYPABLE_COMMAND_MAP.get(name.as_str()) { let mut cx = compositor::Context { @@ -186,21 +186,21 @@ impl MappableCommand { } } } - MappableCommand::Static { fun, .. } => (fun)(cx), + Self::Static { fun, .. } => (fun)(cx), } } pub fn name(&self) -> &str { match &self { - MappableCommand::Typable { name, .. } => name, - MappableCommand::Static { name, .. } => name, + Self::Typable { name, .. } => name, + Self::Static { name, .. } => name, } } pub fn doc(&self) -> &str { match &self { - MappableCommand::Typable { doc, .. } => doc, - MappableCommand::Static { doc, .. } => doc, + Self::Typable { doc, .. } => doc, + Self::Static { doc, .. } => doc, } } @@ -3494,11 +3494,9 @@ pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> { match op { ResourceOp::Create(op) => { let path = op.uri.to_file_path().unwrap(); - let ignore_if_exists = if let Some(options) = &op.options { + let ignore_if_exists = op.options.as_ref().map_or(false, |options| { !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false) - } else { - false - }; + }); if ignore_if_exists && path.exists() { Ok(()) } else { @@ -3508,11 +3506,12 @@ pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> { ResourceOp::Delete(op) => { let path = op.uri.to_file_path().unwrap(); if path.is_dir() { - let recursive = if let Some(options) = &op.options { - options.recursive.unwrap_or(false) - } else { - false - }; + let recursive = op + .options + .as_ref() + .and_then(|options| options.recursive) + .unwrap_or(false); + if recursive { fs::remove_dir_all(&path) } else { @@ -3527,11 +3526,9 @@ pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> { ResourceOp::Rename(op) => { let from = op.old_uri.to_file_path().unwrap(); let to = op.new_uri.to_file_path().unwrap(); - let ignore_if_exists = if let Some(options) = &op.options { + let ignore_if_exists = op.options.as_ref().map_or(false, |options| { !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false) - } else { - false - }; + }); if ignore_if_exists && to.exists() { Ok(()) } else { diff --git a/helix-term/src/job.rs b/helix-term/src/job.rs index 4fa38174..f5a0a425 100644 --- a/helix-term/src/job.rs +++ b/helix-term/src/job.rs @@ -22,8 +22,8 @@ pub struct Jobs { } impl Job { - pub fn new> + Send + 'static>(f: F) -> Job { - Job { + pub fn new> + Send + 'static>(f: F) -> Self { + Self { future: f.map(|r| r.map(|()| None)).boxed(), wait: false, } @@ -31,22 +31,22 @@ impl Job { pub fn with_callback> + Send + 'static>( f: F, - ) -> Job { - Job { + ) -> Self { + Self { future: f.map(|r| r.map(Some)).boxed(), wait: false, } } - pub fn wait_before_exiting(mut self) -> Job { + pub fn wait_before_exiting(mut self) -> Self { self.wait = true; self } } impl Jobs { - pub fn new() -> Jobs { - Jobs::default() + pub fn new() -> Self { + Self::default() } pub fn spawn> + Send + 'static>(&mut self, f: F) { diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index c4bd25ed..79a06206 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -344,7 +344,7 @@ pub struct Keymap { impl Keymap { pub fn new(root: KeyTrie) -> Self { - Keymap { + Self { root, state: Vec::new(), sticky: None, @@ -368,7 +368,7 @@ impl Keymap { /// key cancels pending keystrokes. If there are no pending keystrokes but a /// sticky node is in use, it will be cleared. pub fn get(&mut self, key: KeyEvent) -> KeymapResult { - if let key!(Esc) = key { + if key!(Esc) == key { if !self.state.is_empty() { return KeymapResult::new( // Note that Esc is not included here @@ -477,7 +477,7 @@ impl DerefMut for Keymaps { } impl Default for Keymaps { - fn default() -> Keymaps { + fn default() -> Self { let normal = keymap!({ "Normal mode" "h" | "left" => move_char_left, "j" | "down" => move_line_down, @@ -784,7 +784,7 @@ impl Default for Keymaps { "C-x" => completion, "C-r" => insert_register, }); - Keymaps(hashmap!( + Self(hashmap!( Mode::Normal => Keymap::new(normal), Mode::Select => Keymap::new(select), Mode::Insert => Keymap::new(insert), diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 274330c0..c9ed3b4a 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -158,7 +158,7 @@ impl Completion { let resolved_additional_text_edits = if item.additional_text_edits.is_some() { None } else { - Completion::resolve_completion_item(doc, item.clone()) + Self::resolve_completion_item(doc, item.clone()) .and_then(|item| item.additional_text_edits) }; diff --git a/helix-tui/src/widgets/block.rs b/helix-tui/src/widgets/block.rs index d43ad41d..26223c3e 100644 --- a/helix-tui/src/widgets/block.rs +++ b/helix-tui/src/widgets/block.rs @@ -15,12 +15,12 @@ pub enum BorderType { } impl BorderType { - pub fn line_symbols(border_type: BorderType) -> line::Set { + pub fn line_symbols(border_type: Self) -> line::Set { match border_type { - BorderType::Plain => line::NORMAL, - BorderType::Rounded => line::ROUNDED, - BorderType::Double => line::DOUBLE, - BorderType::Thick => line::THICK, + Self::Plain => line::NORMAL, + Self::Rounded => line::ROUNDED, + Self::Double => line::DOUBLE, + Self::Thick => line::THICK, } } } diff --git a/helix-tui/src/widgets/paragraph.rs b/helix-tui/src/widgets/paragraph.rs index 8bafbb9a..4e839162 100644 --- a/helix-tui/src/widgets/paragraph.rs +++ b/helix-tui/src/widgets/paragraph.rs @@ -166,7 +166,7 @@ impl<'a> Widget for Paragraph<'a> { Box::new(WordWrapper::new(&mut styled, text_area.width, trim)) } else { let mut line_composer = Box::new(LineTruncator::new(&mut styled, text_area.width)); - if let Alignment::Left = self.alignment { + if self.alignment == Alignment::Left { line_composer.set_horizontal_offset(self.scroll.1); } line_composer diff --git a/helix-view/src/info.rs b/helix-view/src/info.rs index b5a002fa..73856154 100644 --- a/helix-view/src/info.rs +++ b/helix-view/src/info.rs @@ -16,7 +16,7 @@ pub struct Info { } impl Info { - pub fn new(title: &str, body: Vec<(&str, BTreeSet)>) -> Info { + pub fn new(title: &str, body: Vec<(&str, BTreeSet)>) -> Self { let body = body .into_iter() .map(|(desc, events)| { @@ -38,7 +38,7 @@ impl Info { ); } - Info { + Self { title: title.to_string(), width: text.lines().map(|l| l.width()).max().unwrap() as u16, height: body.len() as u16, -- cgit v1.2.3-70-g09d2 From f88c077f992bbfc5ea3623441a9e2b2a0e9ca2b2 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Mon, 7 Feb 2022 14:03:04 +0900 Subject: Replace tendril with smartstring Slightly smaller API surface, less dependencies. --- Cargo.lock | 56 ++++++++++------------------------- helix-core/Cargo.toml | 2 +- helix-core/src/auto_pairs.rs | 14 +++++---- helix-core/src/increment/date_time.rs | 2 +- helix-core/src/increment/number.rs | 10 +++---- helix-core/src/lib.rs | 5 +++- helix-core/src/transaction.rs | 24 ++++++++------- helix-lsp/src/client.rs | 2 +- helix-term/src/commands.rs | 32 +++++++++++--------- xtask/src/main.rs | 2 +- 10 files changed, 69 insertions(+), 80 deletions(-) (limited to 'helix-core/src/increment') diff --git a/Cargo.lock b/Cargo.lock index 3253e6cd..360ebcfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,16 +246,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - [[package]] name = "futures-core" version = "0.3.21" @@ -383,7 +373,7 @@ dependencies = [ "similar", "slotmap", "smallvec", - "tendril", + "smartstring", "toml", "tree-sitter", "unicode-general-category", @@ -606,12 +596,6 @@ dependencies = [ "url", ] -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - [[package]] name = "matches" version = "0.1.9" @@ -655,12 +639,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "new_debug_unreachable" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" - [[package]] name = "ntapi" version = "0.3.6" @@ -1010,6 +988,21 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +[[package]] +name = "smartstring" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31aa6a31c0c2b21327ce875f7e8952322acfcfd0c27569a6e18a647281352c9b" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "str-buf" version = "1.0.5" @@ -1027,17 +1020,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "tendril" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9ef557cb397a4f0a5a3a628f06515f78563f2209e64d47055d9dc6052bf5e33" -dependencies = [ - "futf", - "mac", - "utf-8", -] - [[package]] name = "thiserror" version = "1.0.30" @@ -1212,12 +1194,6 @@ dependencies = [ "serde", ] -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "version_check" version = "0.9.4" diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index 3a1f9b3e..7ff91cfd 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -17,7 +17,7 @@ helix-syntax = { version = "0.6", path = "../helix-syntax" } ropey = "1.3" smallvec = "1.8" -tendril = "0.4.2" +smartstring = "0.2.9" unicode-segmentation = "1.9" unicode-width = "0.1" unicode-general-category = "0.5" diff --git a/helix-core/src/auto_pairs.rs b/helix-core/src/auto_pairs.rs index 40802680..f4359a34 100644 --- a/helix-core/src/auto_pairs.rs +++ b/helix-core/src/auto_pairs.rs @@ -214,7 +214,9 @@ fn handle_open( let change = match next_char { Some(ch) if !close_before.contains(ch) => { len_inserted = open.len_utf8(); - (cursor, cursor, Some(Tendril::from_char(open))) + let mut tendril = Tendril::new(); + tendril.push(open); + (cursor, cursor, Some(tendril)) } // None | Some(ch) if close_before.contains(ch) => {} _ => { @@ -252,7 +254,9 @@ fn handle_close(doc: &Rope, selection: &Selection, _open: char, close: char) -> (cursor, cursor, None) // no-op } else { len_inserted += close.len_utf8(); - (cursor, cursor, Some(Tendril::from_char(close))) + let mut tendril = Tendril::new(); + tendril.push(close); + (cursor, cursor, Some(tendril)) }; let next_range = get_next_range(doc, start_range, offs, close, len_inserted); @@ -290,15 +294,15 @@ fn handle_same( // return transaction that moves past close (cursor, cursor, None) // no-op } else { - let mut pair = Tendril::with_capacity(2 * token.len_utf8() as u32); - pair.push_char(token); + let mut pair = Tendril::new(); + pair.push(token); // for equal pairs, don't insert both open and close if either // side has a non-pair char if (next_char.is_none() || close_before.contains(next_char.unwrap())) && (prev_char.is_none() || open_before.contains(prev_char.unwrap())) { - pair.push_char(token); + pair.push(token); } len_inserted += pair.len(); diff --git a/helix-core/src/increment/date_time.rs b/helix-core/src/increment/date_time.rs index 1703c3ba..91fa5963 100644 --- a/helix-core/src/increment/date_time.rs +++ b/helix-core/src/increment/date_time.rs @@ -451,7 +451,7 @@ mod test { .unwrap() .increment(amount) .1, - expected.into() + Tendril::from(expected) ); } } diff --git a/helix-core/src/increment/number.rs b/helix-core/src/increment/number.rs index a19b7e75..57171f67 100644 --- a/helix-core/src/increment/number.rs +++ b/helix-core/src/increment/number.rs @@ -371,7 +371,7 @@ mod test { .unwrap() .increment(amount) .1, - expected.into() + Tendril::from(expected) ); } } @@ -398,7 +398,7 @@ mod test { .unwrap() .increment(amount) .1, - expected.into() + Tendril::from(expected) ); } } @@ -426,7 +426,7 @@ mod test { .unwrap() .increment(amount) .1, - expected.into() + Tendril::from(expected) ); } } @@ -472,7 +472,7 @@ mod test { .unwrap() .increment(amount) .1, - expected.into() + Tendril::from(expected) ); } } @@ -500,7 +500,7 @@ mod test { .unwrap() .increment(amount) .1, - expected.into() + Tendril::from(expected) ); } } diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index 7fd23b97..0b03ead8 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -212,7 +212,10 @@ use etcetera::base_strategy::{choose_base_strategy, BaseStrategy}; pub use ropey::{Rope, RopeBuilder, RopeSlice}; -pub use tendril::StrTendril as Tendril; +// pub use tendril::StrTendril as Tendril; +pub use smartstring::SmartString; + +pub type Tendril = SmartString; #[doc(inline)] pub use {regex, tree_sitter}; diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs index 30995e8c..2e34a986 100644 --- a/helix-core/src/transaction.rs +++ b/helix-core/src/transaction.rs @@ -85,7 +85,7 @@ impl ChangeSet { let new_last = match self.changes.as_mut_slice() { [.., Insert(prev)] | [.., Insert(prev), Delete(_)] => { - prev.push_tendril(&fragment); + prev.push_str(&fragment); return; } [.., last @ Delete(_)] => std::mem::replace(last, Insert(fragment)), @@ -189,7 +189,7 @@ impl ChangeSet { // TODO: cover this with a test // figure out the byte index of the truncated string end let (pos, _) = s.char_indices().nth(j).unwrap(); - s.pop_front(pos as u32); + s.replace_range(0..pos, ""); head_a = Some(Insert(s)); head_b = changes_b.next(); } @@ -211,9 +211,11 @@ impl ChangeSet { Ordering::Greater => { // figure out the byte index of the truncated string end let (pos, _) = s.char_indices().nth(j).unwrap(); - let pos = pos as u32; - changes.insert(s.subtendril(0, pos)); - head_a = Some(Insert(s.subtendril(pos, s.len() as u32 - pos))); + let mut before = s; + let after = before.split_off(pos); + + changes.insert(before); + head_a = Some(Insert(after)); head_b = changes_b.next(); } } @@ -277,7 +279,7 @@ impl ChangeSet { } Delete(n) => { let text = Cow::from(original_doc.slice(pos..pos + *n)); - changes.insert(Tendril::from_slice(&text)); + changes.insert(Tendril::from(text.as_ref())); pos += n; } Insert(s) => { @@ -710,19 +712,19 @@ mod test { #[test] fn optimized_composition() { let mut state = State::new("".into()); - let t1 = Transaction::insert(&state.doc, &state.selection, Tendril::from_char('h')); + let t1 = Transaction::insert(&state.doc, &state.selection, Tendril::from("h")); t1.apply(&mut state.doc); state.selection = state.selection.clone().map(t1.changes()); - let t2 = Transaction::insert(&state.doc, &state.selection, Tendril::from_char('e')); + let t2 = Transaction::insert(&state.doc, &state.selection, Tendril::from("e")); t2.apply(&mut state.doc); state.selection = state.selection.clone().map(t2.changes()); - let t3 = Transaction::insert(&state.doc, &state.selection, Tendril::from_char('l')); + let t3 = Transaction::insert(&state.doc, &state.selection, Tendril::from("l")); t3.apply(&mut state.doc); state.selection = state.selection.clone().map(t3.changes()); - let t4 = Transaction::insert(&state.doc, &state.selection, Tendril::from_char('l')); + let t4 = Transaction::insert(&state.doc, &state.selection, Tendril::from("l")); t4.apply(&mut state.doc); state.selection = state.selection.clone().map(t4.changes()); - let t5 = Transaction::insert(&state.doc, &state.selection, Tendril::from_char('o')); + let t5 = Transaction::insert(&state.doc, &state.selection, Tendril::from("o")); t5.apply(&mut state.doc); state.selection = state.selection.clone().map(t5.changes()); diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index c80f70b5..15cbca0e 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -438,7 +438,7 @@ impl Client { changes.push(lsp::TextDocumentContentChangeEvent { range: Some(lsp::Range::new(start, end)), - text: s.into(), + text: s.to_string(), range_length: None, }); } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index d0882728..9918d26c 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2769,7 +2769,7 @@ pub mod cmd { let mut fragments: Vec<_> = selection .fragments(text) - .map(|fragment| Tendril::from_slice(&fragment)) + .map(|fragment| Tendril::from(fragment.as_ref())) .collect(); fragments.sort_by(match reverse { @@ -3921,7 +3921,7 @@ fn try_restore_indent(doc: &mut Document, view_id: ViewId) { if let [Operation::Retain(move_pos), Operation::Insert(ref inserted_str), Operation::Retain(_)] = changes { - move_pos + inserted_str.len32() as usize == pos + move_pos + inserted_str.len() == pos && inserted_str.starts_with('\n') && inserted_str.chars().skip(1).all(char_is_whitespace) && pos == line_end_pos // ensure no characters exists after current position @@ -4513,7 +4513,8 @@ pub mod insert { #[allow(clippy::unnecessary_wraps)] // need to use Option<> because of the Hook signature fn insert(doc: &Rope, selection: &Selection, ch: char) -> Option { let cursors = selection.clone().cursors(doc.slice(..)); - let t = Tendril::from_char(ch); + let mut t = Tendril::new(); + t.push(ch); let transaction = Transaction::insert(doc, &cursors, t); Some(transaction) } @@ -5015,12 +5016,12 @@ fn replace_with_yanked(cx: &mut Context) { let repeat = std::iter::repeat( values .last() - .map(|value| Tendril::from_slice(&value.repeat(count))) + .map(|value| Tendril::from(&value.repeat(count))) .unwrap(), ); let mut values = values .iter() - .map(|value| Tendril::from_slice(&value.repeat(count))) + .map(|value| Tendril::from(&value.repeat(count))) .chain(repeat); let selection = doc.selection(view.id); let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { @@ -5530,7 +5531,7 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) { let selection = doc.selection(view.id); let mut fragments: Vec<_> = selection .fragments(text) - .map(|fragment| Tendril::from_slice(&fragment)) + .map(|fragment| Tendril::from(fragment.as_ref())) .collect(); let group = count @@ -5891,8 +5892,12 @@ fn surround_add(cx: &mut Context) { let mut changes = Vec::with_capacity(selection.len() * 2); for range in selection.iter() { - changes.push((range.from(), range.from(), Some(Tendril::from_char(open)))); - changes.push((range.to(), range.to(), Some(Tendril::from_char(close)))); + let mut o = Tendril::new(); + o.push(open); + let mut c = Tendril::new(); + c.push(close); + changes.push((range.from(), range.from(), Some(o))); + changes.push((range.to(), range.to(), Some(c))); } let transaction = Transaction::change(doc.text(), changes.into_iter()); @@ -5921,11 +5926,9 @@ fn surround_replace(cx: &mut Context) { let transaction = Transaction::change( doc.text(), change_pos.iter().enumerate().map(|(i, &pos)| { - ( - pos, - pos + 1, - Some(Tendril::from_char(if i % 2 == 0 { open } else { close })), - ) + let mut t = Tendril::new(); + t.push(if i % 2 == 0 { open } else { close }); + (pos, pos + 1, Some(t)) }), ); doc.apply(&transaction, view.id); @@ -6065,8 +6068,9 @@ fn shell_impl( log::error!("Shell error: {}", String::from_utf8_lossy(&output.stderr)); } - let tendril = Tendril::try_from_byte_slice(&output.stdout) + let str = std::str::from_utf8(&output.stdout) .map_err(|_| anyhow!("Process did not output valid UTF-8"))?; + let tendril = Tendril::from(str); Ok((tendril, output.status.success())) } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 7256653a..d24a29cc 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -120,7 +120,7 @@ pub mod md_gen { } fn md_table_row(cols: &[String]) -> String { - "| ".to_owned() + &cols.join(" | ") + " |\n" + format!("| {} |\n", cols.join(" | ")) } fn md_mono(s: &str) -> String { -- cgit v1.2.3-70-g09d2