From 4ba7bb636953c8bc78d8af24587f75b007218db8 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 20 Jun 2018 14:43:05 +0300 Subject: [PATCH 01/87] initial --- .gitignore | 3 + Cargo.toml | 11 +++ LICENSE-APACHE | 201 +++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE-MIT | 23 ++++++ README.md | 10 +++ src/lib.rs | 186 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 434 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 README.md create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..693699042b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000000..5aa9632e9e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "text_unit" +version = "0.1.0" +authors = ["Aleksey Kladov "] +description = "Newtypes for text offsets" +license = "MIT OR Apache-2.0" +repository = "https://github.com/matklad/text_unit" +documentation = "https://docs.rs/text_unit" + +[dependencies] +serde = { version = "1", optional = true } diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000000..16fe87b06e --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000000..31aa79387f --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..6887ccf032 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# text_unit + +[![Build Status](https://travis-ci.org/matklad/text_unit.svg?branch=master)](https://travis-ci.org/matklad/text_unit) +[![Crates.io](https://img.shields.io/crates/v/text_unit.svg)](https://crates.io/crates/text_unit) +[![API reference](https://docs.rs/text_unit/badge.svg)](https://docs.rs/text_unit/) + + +A library that provides newtype wrappers for `u32` and `(u32, u32)` for use as text offsets. + +See the [docs](https://docs.rs/text_unit/) for more. diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000000..6c6444685a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,186 @@ +#[cfg(feature = "serde")] +extern crate serde; + +use std::{fmt, ops}; + + +/// An offset into text. +/// Offset is represented as `u32` storing number of utf8-bytes, +/// but most of the clients should treat it like opaque measure. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct TextUnit(u32); + +impl TextUnit { + /// `TextUnit` equal to the length of this char. + pub fn of_char(c: char) -> TextUnit { + TextUnit(c.len_utf8() as u32) + } + + /// `TextUnit` equal to the length of this string. + /// + /// # Panics + /// Panics if the length of the string is greater than `u32::max_value()` + pub fn of_str(s: &str) -> TextUnit { + if s.len() > u32::max_value() as usize { + panic!("string is to long") + } + TextUnit(s.len() as u32) + } +} + +impl fmt::Debug for TextUnit { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + ::fmt(self, f) + } +} + +impl fmt::Display for TextUnit { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl From for u32 { + fn from(tu: TextUnit) -> u32 { + tu.0 + } +} + +impl From for TextUnit { + fn from(tu: u32) -> TextUnit { + TextUnit(tu) + } +} + +impl ops::Add for TextUnit { + type Output = TextUnit; + fn add(self, rhs: TextUnit) -> TextUnit { + TextUnit(self.0 + rhs.0) + } +} + +impl ops::AddAssign for TextUnit { + fn add_assign(&mut self, rhs: TextUnit) { + self.0 += rhs.0 + } +} + +impl ops::Sub for TextUnit { + type Output = TextUnit; + fn sub(self, rhs: TextUnit) -> TextUnit { + TextUnit(self.0 - rhs.0) + } +} + +impl ops::SubAssign for TextUnit { + fn sub_assign(&mut self, rhs: TextUnit) { + self.0 -= rhs.0 + } +} + +/// A range in the text, represented as a pair of `TextUnit`s. +/// +/// # Panics +/// Slicing a `&str` with `TextRange` panics if the result is +/// not a valid utf8 string. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct TextRange { + start: TextUnit, + end: TextUnit, +} + +impl fmt::Debug for TextRange { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + ::fmt(self, f) + } +} + +impl fmt::Display for TextRange { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "[{}; {})", self.start(), self.end()) + } +} + +impl TextRange { + /// The left-inclusive range (`[from..to)`) between to points in the text + pub fn from_to(from: TextUnit, to: TextUnit) -> TextRange { + assert!(from <= to, "Invalid text range [{}; {})", from, to); + TextRange { + start: from, + end: to, + } + } + + /// The left-inclusive range (`[offset..offset + len)`) between to points in the text + pub fn offset_len(offset: TextUnit, len: TextUnit) -> TextRange { + TextRange::from_to(offset, offset + len) + } + + /// The inclusive start of this range + pub fn start(&self) -> TextUnit { + self.start + } + + /// The exclusive end of this range + pub fn end(&self) -> TextUnit { + self.end + } + + /// The length of this range + pub fn len(&self) -> TextUnit { + self.end - self.start + } + + /// Is this range empty of any content? + pub fn is_empty(&self) -> bool { + self.start() == self.end() + } +} + +impl ops::Index for str { + type Output = str; + + fn index(&self, index: TextRange) -> &str { + &self[index.start().0 as usize..index.end().0 as usize] + } +} + +impl ops::Index for String { + type Output = str; + + fn index(&self, index: TextRange) -> &str { + &self.as_str()[index] + } +} + +#[cfg(feature = "serde")] +mod serde_impls { + use serde::{Serialize, Serializer, Deserialize, Deserializer}; + use {TextUnit, TextRange}; + + impl Serialize for TextUnit { + fn serialize(&self, serializer: S) -> Result { + self.0.serialize(serializer) + } + } + + impl<'de> Deserialize<'de> for TextUnit { + fn deserialize>(deserializer: D) -> Result { + let value = Deserialize::deserialize(deserializer)?; + Ok(TextUnit(value)) + } + } + + impl Serialize for TextRange { + fn serialize(&self, serializer: S) -> Result { + (self.start, self.end).serialize(serializer) + } + } + + impl<'de> Deserialize<'de> for TextRange { + fn deserialize>(deserializer: D) -> Result { + let (start, end) = Deserialize::deserialize(deserializer)?; + Ok(TextRange { start, end }) + } + } +} From 6f4c8ed5f260fa684bd8cd74f69b47482d8200ca Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 20 Jun 2018 14:44:50 +0300 Subject: [PATCH 02/87] CI --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..a7d0a0bc81 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,2 @@ +language: rust + From 20486e99ee8f43f80981bb6c7f060f8591e5cb36 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 28 Jul 2018 11:54:27 +0300 Subject: [PATCH 03/87] Add more impls --- Cargo.toml | 2 +- src/lib.rs | 78 +++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 63 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5aa9632e9e..6785d8d6dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "text_unit" -version = "0.1.0" +version = "0.1.1" authors = ["Aleksey Kladov "] description = "Newtypes for text offsets" license = "MIT OR Apache-2.0" diff --git a/src/lib.rs b/src/lib.rs index 6c6444685a..5bb15e1d2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ #[cfg(feature = "serde")] extern crate serde; -use std::{fmt, ops}; +use std::{fmt, ops, iter}; /// An offset into text. @@ -52,29 +52,63 @@ impl From for TextUnit { } } -impl ops::Add for TextUnit { +macro_rules! ops_impls { + ($T:ident, $f:ident, $op:tt, $AT:ident, $af:ident) => { + +impl ops::$T for TextUnit { type Output = TextUnit; - fn add(self, rhs: TextUnit) -> TextUnit { - TextUnit(self.0 + rhs.0) + fn $f(self, rhs: TextUnit) -> TextUnit { + TextUnit(self.0 $op rhs.0) } } -impl ops::AddAssign for TextUnit { - fn add_assign(&mut self, rhs: TextUnit) { - self.0 += rhs.0 - } -} - -impl ops::Sub for TextUnit { +impl<'a> ops::$T<&'a TextUnit> for TextUnit { type Output = TextUnit; - fn sub(self, rhs: TextUnit) -> TextUnit { - TextUnit(self.0 - rhs.0) + fn $f(self, rhs: &'a TextUnit) -> TextUnit { + ops::$T::$f(self, *rhs) } } -impl ops::SubAssign for TextUnit { - fn sub_assign(&mut self, rhs: TextUnit) { - self.0 -= rhs.0 +impl<'a> ops::$T for &'a TextUnit { + type Output = TextUnit; + fn $f(self, rhs: TextUnit) -> TextUnit { + ops::$T::$f(*self, rhs) + } +} + +impl<'a, 'b> ops::$T<&'a TextUnit> for &'b TextUnit { + type Output = TextUnit; + fn $f(self, rhs: &'a TextUnit) -> TextUnit { + ops::$T::$f(*self, *rhs) + } +} + +impl ops::$AT for TextUnit { + fn $af(&mut self, rhs: TextUnit) { + self.0 = self.0 $op rhs.0 + } +} + +impl<'a> ops::$AT<&'a TextUnit> for TextUnit { + fn $af(&mut self, rhs: &'a TextUnit) { + ops::$AT::$af(self, *rhs) + } +} + }; +} + +ops_impls!(Add, add, +, AddAssign, add_assign); +ops_impls!(Sub, sub, -, SubAssign, sub_assign); + +impl<'a> iter::Sum<&'a TextUnit> for TextUnit { + fn sum>(iter: I) -> TextUnit { + iter.fold(TextUnit::from(0), ops::Add::add) + } +} + +impl iter::Sum for TextUnit { + fn sum>(iter: I) -> TextUnit { + iter.fold(TextUnit::from(0), ops::Add::add) } } @@ -184,3 +218,15 @@ mod serde_impls { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sum() { + let xs: Vec = vec![0.into(), 1.into(), 2.into()]; + assert_eq!(xs.iter().sum::(), 3.into()); + assert_eq!(xs.into_iter().sum::(), 3.into()); + } +} From b26dc781c2907ed59cdcc5f85a7bb7998c2528e4 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 31 Jul 2018 21:40:07 +0300 Subject: [PATCH 04/87] Add inline(always) It's useful for cross-crate inlining --- Cargo.toml | 2 +- src/lib.rs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6785d8d6dd..5b7e1c4b54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "text_unit" -version = "0.1.1" +version = "0.1.2" authors = ["Aleksey Kladov "] description = "Newtypes for text offsets" license = "MIT OR Apache-2.0" diff --git a/src/lib.rs b/src/lib.rs index 5bb15e1d2a..5e34da29f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ pub struct TextUnit(u32); impl TextUnit { /// `TextUnit` equal to the length of this char. + #[inline(always)] pub fn of_char(c: char) -> TextUnit { TextUnit(c.len_utf8() as u32) } @@ -20,6 +21,7 @@ impl TextUnit { /// /// # Panics /// Panics if the length of the string is greater than `u32::max_value()` + #[inline(always)] pub fn of_str(s: &str) -> TextUnit { if s.len() > u32::max_value() as usize { panic!("string is to long") @@ -35,18 +37,21 @@ impl fmt::Debug for TextUnit { } impl fmt::Display for TextUnit { + #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) } } impl From for u32 { + #[inline(always)] fn from(tu: TextUnit) -> u32 { tu.0 } } impl From for TextUnit { + #[inline(always)] fn from(tu: u32) -> TextUnit { TextUnit(tu) } @@ -57,6 +62,7 @@ macro_rules! ops_impls { impl ops::$T for TextUnit { type Output = TextUnit; + #[inline(always)] fn $f(self, rhs: TextUnit) -> TextUnit { TextUnit(self.0 $op rhs.0) } @@ -64,6 +70,7 @@ impl ops::$T for TextUnit { impl<'a> ops::$T<&'a TextUnit> for TextUnit { type Output = TextUnit; + #[inline(always)] fn $f(self, rhs: &'a TextUnit) -> TextUnit { ops::$T::$f(self, *rhs) } @@ -71,6 +78,7 @@ impl<'a> ops::$T<&'a TextUnit> for TextUnit { impl<'a> ops::$T for &'a TextUnit { type Output = TextUnit; + #[inline(always)] fn $f(self, rhs: TextUnit) -> TextUnit { ops::$T::$f(*self, rhs) } @@ -78,18 +86,21 @@ impl<'a> ops::$T for &'a TextUnit { impl<'a, 'b> ops::$T<&'a TextUnit> for &'b TextUnit { type Output = TextUnit; + #[inline(always)] fn $f(self, rhs: &'a TextUnit) -> TextUnit { ops::$T::$f(*self, *rhs) } } impl ops::$AT for TextUnit { + #[inline(always)] fn $af(&mut self, rhs: TextUnit) { self.0 = self.0 $op rhs.0 } } impl<'a> ops::$AT<&'a TextUnit> for TextUnit { + #[inline(always)] fn $af(&mut self, rhs: &'a TextUnit) { ops::$AT::$af(self, *rhs) } @@ -137,6 +148,7 @@ impl fmt::Display for TextRange { impl TextRange { /// The left-inclusive range (`[from..to)`) between to points in the text + #[inline(always)] pub fn from_to(from: TextUnit, to: TextUnit) -> TextRange { assert!(from <= to, "Invalid text range [{}; {})", from, to); TextRange { @@ -146,26 +158,31 @@ impl TextRange { } /// The left-inclusive range (`[offset..offset + len)`) between to points in the text + #[inline(always)] pub fn offset_len(offset: TextUnit, len: TextUnit) -> TextRange { TextRange::from_to(offset, offset + len) } /// The inclusive start of this range + #[inline(always)] pub fn start(&self) -> TextUnit { self.start } /// The exclusive end of this range + #[inline(always)] pub fn end(&self) -> TextUnit { self.end } /// The length of this range + #[inline(always)] pub fn len(&self) -> TextUnit { self.end - self.start } /// Is this range empty of any content? + #[inline(always)] pub fn is_empty(&self) -> bool { self.start() == self.end() } From 3b379f8d9945669eb9c0fd0b1beeec2782a2d973 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 22 Aug 2018 11:36:06 +0300 Subject: [PATCH 05/87] ranges arithmetics --- Cargo.toml | 2 +- src/lib.rs | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5b7e1c4b54..6122a304dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "text_unit" -version = "0.1.2" +version = "0.1.3" authors = ["Aleksey Kladov "] description = "Newtypes for text offsets" license = "MIT OR Apache-2.0" diff --git a/src/lib.rs b/src/lib.rs index 5e34da29f0..00027ce69e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,7 +57,7 @@ impl From for TextUnit { } } -macro_rules! ops_impls { +macro_rules! unit_ops_impls { ($T:ident, $f:ident, $op:tt, $AT:ident, $af:ident) => { impl ops::$T for TextUnit { @@ -108,8 +108,73 @@ impl<'a> ops::$AT<&'a TextUnit> for TextUnit { }; } -ops_impls!(Add, add, +, AddAssign, add_assign); -ops_impls!(Sub, sub, -, SubAssign, sub_assign); +macro_rules! range_ops_impls { + ($T:ident, $f:ident, $op:tt, $AT:ident, $af:ident) => { + +impl ops::$T for TextRange { + type Output = TextRange; + #[inline(always)] + fn $f(self, rhs: TextUnit) -> TextRange { + TextRange::from_to( + self.start() $op rhs, + self.end() $op rhs, + ) + } +} + +impl<'a> ops::$T<&'a TextUnit> for TextRange { + type Output = TextRange; + #[inline(always)] + fn $f(self, rhs: &'a TextUnit) -> TextRange { + TextRange::from_to( + self.start() $op rhs, + self.end() $op rhs, + ) + } +} + +impl<'a> ops::$T for &'a TextRange { + type Output = TextRange; + #[inline(always)] + fn $f(self, rhs: TextUnit) -> TextRange { + TextRange::from_to( + self.start() $op rhs, + self.end() $op rhs, + ) + } +} + +impl<'a, 'b> ops::$T<&'a TextUnit> for &'b TextRange { + type Output = TextRange; + #[inline(always)] + fn $f(self, rhs: &'a TextUnit) -> TextRange { + TextRange::from_to( + self.start() $op rhs, + self.end() $op rhs, + ) + } +} + +impl ops::$AT for TextRange { + #[inline(always)] + fn $af(&mut self, rhs: TextUnit) { + *self = *self $op rhs + } +} + +impl<'a> ops::$AT<&'a TextUnit> for TextRange { + #[inline(always)] + fn $af(&mut self, rhs: &'a TextUnit) { + *self = *self $op rhs + } +} + }; +} + +unit_ops_impls!(Add, add, +, AddAssign, add_assign); +unit_ops_impls!(Sub, sub, -, SubAssign, sub_assign); +range_ops_impls!(Add, add, +, AddAssign, add_assign); +range_ops_impls!(Sub, sub, -, SubAssign, sub_assign); impl<'a> iter::Sum<&'a TextUnit> for TextUnit { fn sum>(iter: I) -> TextUnit { @@ -246,4 +311,18 @@ mod tests { assert_eq!(xs.iter().sum::(), 3.into()); assert_eq!(xs.into_iter().sum::(), 3.into()); } + + #[test] + fn test_ops() { + let r = TextRange::from_to(10.into(), 20.into()); + let u: TextUnit = 5.into(); + assert_eq!( + r + u, + TextRange::from_to(15.into(), 25.into()), + ); + assert_eq!( + r - u, + TextRange::from_to(5.into(), 15.into()), + ); + } } From 16d52857cc52c143da7e2ca398d9a6e5929956eb Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 28 Aug 2018 20:56:10 +0300 Subject: [PATCH 06/87] checked ops --- Cargo.toml | 2 +- src/lib.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6122a304dd..99fed3d728 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "text_unit" -version = "0.1.3" +version = "0.1.4" authors = ["Aleksey Kladov "] description = "Newtypes for text offsets" license = "MIT OR Apache-2.0" diff --git a/src/lib.rs b/src/lib.rs index 00027ce69e..28b2e4d10e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,11 @@ impl TextUnit { } TextUnit(s.len() as u32) } + + #[inline(always)] + pub fn checked_sub(self, other: TextUnit) -> Option { + self.0.checked_sub(other.0).map(TextUnit) + } } impl fmt::Debug for TextUnit { @@ -199,6 +204,17 @@ pub struct TextRange { end: TextUnit, } +impl TextRange { + #[inline(always)] + pub fn checked_sub(self, other: TextUnit) -> Option { + let res = TextRange::offset_len( + self.start().checked_sub(other)?, + self.len() + ); + Some(res) + } +} + impl fmt::Debug for TextRange { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { ::fmt(self, f) @@ -325,4 +341,15 @@ mod tests { TextRange::from_to(5.into(), 15.into()), ); } + + #[test] + fn test_checked_ops() { + let x: TextUnit = 1.into(); + assert_eq!(x.checked_sub(1.into()), Some(0.into())); + assert_eq!(x.checked_sub(2.into()), None); + + let r = TextRange::from_to(1.into(), 2.into()); + assert_eq!(r.checked_sub(1.into()), Some(TextRange::from_to(0.into(), 1.into()))); + assert_eq!(x.checked_sub(2.into()), None); + } } From 69123863a133fb699257fb51b9f570256e3e2f84 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 30 Oct 2018 21:13:51 +0300 Subject: [PATCH 07/87] a couple of utility methods --- Cargo.toml | 2 +- src/lib.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 99fed3d728..274d6e64bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "text_unit" -version = "0.1.4" +version = "0.1.5" authors = ["Aleksey Kladov "] description = "Newtypes for text offsets" license = "MIT OR Apache-2.0" diff --git a/src/lib.rs b/src/lib.rs index 28b2e4d10e..179fde2142 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ use std::{fmt, ops, iter}; pub struct TextUnit(u32); impl TextUnit { + //TODO: rename to `from_char`: this is not ocaml! /// `TextUnit` equal to the length of this char. #[inline(always)] pub fn of_char(c: char) -> TextUnit { @@ -33,6 +34,21 @@ impl TextUnit { pub fn checked_sub(self, other: TextUnit) -> Option { self.0.checked_sub(other.0).map(TextUnit) } + + #[inline(always)] + pub fn from_usize(size: usize) -> TextUnit { + #[cfg(debug_assertions)] { + if size > u32::max_value() as usize { + panic!("overflow when converting to TextUnit: {}", size) + } + } + (size as u32).into() + } + + #[inline(always)] + pub fn to_usize(self) -> usize { + u32::from(self) as usize + } } impl fmt::Debug for TextUnit { @@ -244,6 +260,7 @@ impl TextRange { TextRange::from_to(offset, offset + len) } + // TODO: pass by value /// The inclusive start of this range #[inline(always)] pub fn start(&self) -> TextUnit { @@ -267,6 +284,12 @@ impl TextRange { pub fn is_empty(&self) -> bool { self.start() == self.end() } + + #[inline(always)] + pub fn is_subrange(&self, other: &TextRange) -> bool { + other.start() <= self.start() + && self.end() <= other.end() + } } impl ops::Index for str { @@ -352,4 +375,13 @@ mod tests { assert_eq!(r.checked_sub(1.into()), Some(TextRange::from_to(0.into(), 1.into()))); assert_eq!(x.checked_sub(2.into()), None); } + + #[test] + fn test_subrange() { + let r1 = TextRange::from_to(2.into(), 4.into()); + let r2 = TextRange::from_to(2.into(), 3.into()); + let r3 = TextRange::from_to(1.into(), 3.into()); + assert!(r2.is_subrange(&r1)); + assert!(!r3.is_subrange(&r1)); + } } From 4832de6aaee33c453982f5764130d15f158f4f8a Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 15 May 2019 18:59:55 +0300 Subject: [PATCH 08/87] restore lost code =/ --- src/lib.rs | 90 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 27 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 179fde2142..73e076923f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,7 @@ #[cfg(feature = "serde")] extern crate serde; -use std::{fmt, ops, iter}; - +use std::{fmt, iter, ops}; /// An offset into text. /// Offset is represented as `u32` storing number of utf8-bytes, @@ -37,7 +36,8 @@ impl TextUnit { #[inline(always)] pub fn from_usize(size: usize) -> TextUnit { - #[cfg(debug_assertions)] { + #[cfg(debug_assertions)] + { if size > u32::max_value() as usize { panic!("overflow when converting to TextUnit: {}", size) } @@ -198,13 +198,13 @@ range_ops_impls!(Add, add, +, AddAssign, add_assign); range_ops_impls!(Sub, sub, -, SubAssign, sub_assign); impl<'a> iter::Sum<&'a TextUnit> for TextUnit { - fn sum>(iter: I) -> TextUnit { + fn sum>(iter: I) -> TextUnit { iter.fold(TextUnit::from(0), ops::Add::add) } } impl iter::Sum for TextUnit { - fn sum>(iter: I) -> TextUnit { + fn sum>(iter: I) -> TextUnit { iter.fold(TextUnit::from(0), ops::Add::add) } } @@ -223,10 +223,7 @@ pub struct TextRange { impl TextRange { #[inline(always)] pub fn checked_sub(self, other: TextUnit) -> Option { - let res = TextRange::offset_len( - self.start().checked_sub(other)?, - self.len() - ); + let res = TextRange::offset_len(self.start().checked_sub(other)?, self.len()); Some(res) } } @@ -287,8 +284,28 @@ impl TextRange { #[inline(always)] pub fn is_subrange(&self, other: &TextRange) -> bool { - other.start() <= self.start() - && self.end() <= other.end() + other.start() <= self.start() && self.end() <= other.end() + } + + #[inline(always)] + pub fn intersection(&self, other: &TextRange) -> Option { + let start = self.start.max(other.start()); + let end = self.end.min(other.end()); + if start <= end { + Some(TextRange::from_to(start, end)) + } else { + None + } + } + + #[inline(always)] + pub fn contains(&self, offset: TextUnit) -> bool { + self.start() <= offset && offset < self.end() + } + + #[inline(always)] + pub fn contains_inclusive(&self, offset: TextUnit) -> bool { + self.start() <= offset && offset <= self.end() } } @@ -310,8 +327,8 @@ impl ops::Index for String { #[cfg(feature = "serde")] mod serde_impls { - use serde::{Serialize, Serializer, Deserialize, Deserializer}; - use {TextUnit, TextRange}; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + use {TextRange, TextUnit}; impl Serialize for TextUnit { fn serialize(&self, serializer: S) -> Result { @@ -344,6 +361,10 @@ mod serde_impls { mod tests { use super::*; + fn r(from: u32, to: u32) -> TextRange { + TextRange::from_to(from.into(), to.into()) + } + #[test] fn test_sum() { let xs: Vec = vec![0.into(), 1.into(), 2.into()]; @@ -353,16 +374,10 @@ mod tests { #[test] fn test_ops() { - let r = TextRange::from_to(10.into(), 20.into()); + let range = r(10, 20); let u: TextUnit = 5.into(); - assert_eq!( - r + u, - TextRange::from_to(15.into(), 25.into()), - ); - assert_eq!( - r - u, - TextRange::from_to(5.into(), 15.into()), - ); + assert_eq!(range + u, r(15, 25)); + assert_eq!(range - u, r(5, 15)); } #[test] @@ -371,17 +386,38 @@ mod tests { assert_eq!(x.checked_sub(1.into()), Some(0.into())); assert_eq!(x.checked_sub(2.into()), None); - let r = TextRange::from_to(1.into(), 2.into()); - assert_eq!(r.checked_sub(1.into()), Some(TextRange::from_to(0.into(), 1.into()))); + assert_eq!(r(1, 2).checked_sub(1.into()), Some(r(0, 1))); assert_eq!(x.checked_sub(2.into()), None); } #[test] fn test_subrange() { - let r1 = TextRange::from_to(2.into(), 4.into()); - let r2 = TextRange::from_to(2.into(), 3.into()); - let r3 = TextRange::from_to(1.into(), 3.into()); + let r1 = r(2, 4); + let r2 = r(2, 3); + let r3 = r(1, 3); assert!(r2.is_subrange(&r1)); assert!(!r3.is_subrange(&r1)); } + + #[test] + fn check_intersection() { + assert_eq!(r(1, 2).intersection(&r(2, 3)), Some(r(2, 2))); + assert_eq!(r(1, 5).intersection(&r(2, 3)), Some(r(2, 3))); + assert_eq!(r(1, 2).intersection(&r(3, 4)), None); + } + + #[test] + fn check_contains() { + assert!(!r(1, 3).contains(0.into())); + assert!(r(1, 3).contains(1.into())); + assert!(r(1, 3).contains(2.into())); + assert!(!r(1, 3).contains(3.into())); + assert!(!r(1, 3).contains(4.into())); + + assert!(!r(1, 3).contains_inclusive(0.into())); + assert!(r(1, 3).contains_inclusive(1.into())); + assert!(r(1, 3).contains_inclusive(2.into())); + assert!(r(1, 3).contains_inclusive(3.into())); + assert!(!r(1, 3).contains_inclusive(4.into())); + } } From c64484f68a4f4addca0ef122e3be1488287800a3 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 15 May 2019 18:45:58 +0300 Subject: [PATCH 09/87] implement RangeBounds for TextRange --- src/lib.rs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 73e076923f..6d08649751 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -220,14 +220,6 @@ pub struct TextRange { end: TextUnit, } -impl TextRange { - #[inline(always)] - pub fn checked_sub(self, other: TextUnit) -> Option { - let res = TextRange::offset_len(self.start().checked_sub(other)?, self.len()); - Some(res) - } -} - impl fmt::Debug for TextRange { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { ::fmt(self, f) @@ -307,6 +299,25 @@ impl TextRange { pub fn contains_inclusive(&self, offset: TextUnit) -> bool { self.start() <= offset && offset <= self.end() } + + #[inline(always)] + pub fn checked_sub(self, other: TextUnit) -> Option { + let res = TextRange::offset_len( + self.start().checked_sub(other)?, + self.len() + ); + Some(res) + } +} + +impl ops::RangeBounds for TextRange { + fn start_bound(&self) -> ops::Bound<&TextUnit> { + ops::Bound::Included(&self.start) + } + + fn end_bound(&self) -> ops::Bound<&TextUnit> { + ops::Bound::Excluded(&self.end) + } } impl ops::Index for str { From d4a9b0e5a765eaa75cf4b5023fc9ee7ffbfcc3b8 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 15 May 2019 19:01:08 +0300 Subject: [PATCH 10/87] bump version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 274d6e64bd..ea1881e6e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "text_unit" -version = "0.1.5" +version = "0.1.8" authors = ["Aleksey Kladov "] description = "Newtypes for text offsets" license = "MIT OR Apache-2.0" From bad3837b265677fc5a1c45b5a3bb313fdb375d48 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 2 Jun 2019 14:36:29 +0300 Subject: [PATCH 11/87] support deepsize --- Cargo.toml | 5 +++-- src/lib.rs | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ea1881e6e6..40afa4311c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "text_unit" -version = "0.1.8" +version = "0.1.9" authors = ["Aleksey Kladov "] description = "Newtypes for text offsets" license = "MIT OR Apache-2.0" @@ -8,4 +8,5 @@ repository = "https://github.com/matklad/text_unit" documentation = "https://docs.rs/text_unit" [dependencies] -serde = { version = "1", optional = true } +serde = { version = "1", optional = true, default_features = false } +deepsize = { version = "0.1", optional = true, default_features = false } diff --git a/src/lib.rs b/src/lib.rs index 6d08649751..48e1920b48 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -368,6 +368,11 @@ mod serde_impls { } } +#[cfg(feature = "deepsize")] +mod deepsize_impls { + deepsize::known_deep_size!(0, crate::TextUnit, crate::TextRange); +} + #[cfg(test)] mod tests { use super::*; From 0258341dcfb5e7f3074490e058652b36e0631abf Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 13 Feb 2020 23:09:23 +0100 Subject: [PATCH 12/87] Add TODOs --- src/lib.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 48e1920b48..c00954c55c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,17 +6,19 @@ use std::{fmt, iter, ops}; /// An offset into text. /// Offset is represented as `u32` storing number of utf8-bytes, /// but most of the clients should treat it like opaque measure. +// BREAK: TextSize(u32) #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct TextUnit(u32); impl TextUnit { - //TODO: rename to `from_char`: this is not ocaml! + // BREAK: consider renaming? /// `TextUnit` equal to the length of this char. #[inline(always)] pub fn of_char(c: char) -> TextUnit { TextUnit(c.len_utf8() as u32) } + // BREAK: consider renaming? /// `TextUnit` equal to the length of this string. /// /// # Panics @@ -233,6 +235,8 @@ impl fmt::Display for TextRange { } impl TextRange { + // BREAK: TextRange::new(from..to)? + // BREAK: TextRange(from, to)? /// The left-inclusive range (`[from..to)`) between to points in the text #[inline(always)] pub fn from_to(from: TextUnit, to: TextUnit) -> TextRange { @@ -249,36 +253,41 @@ impl TextRange { TextRange::from_to(offset, offset + len) } - // TODO: pass by value + // BREAK: pass by value /// The inclusive start of this range #[inline(always)] pub fn start(&self) -> TextUnit { self.start } + // BREAK: pass by value /// The exclusive end of this range #[inline(always)] pub fn end(&self) -> TextUnit { self.end } + // BREAK: pass by value /// The length of this range #[inline(always)] pub fn len(&self) -> TextUnit { self.end - self.start } + // BREAK: pass by value /// Is this range empty of any content? #[inline(always)] pub fn is_empty(&self) -> bool { self.start() == self.end() } + // BREAK: pass by value #[inline(always)] pub fn is_subrange(&self, other: &TextRange) -> bool { other.start() <= self.start() && self.end() <= other.end() } + // BREAK: pass by value #[inline(always)] pub fn intersection(&self, other: &TextRange) -> Option { let start = self.start.max(other.start()); @@ -290,11 +299,13 @@ impl TextRange { } } + // BREAK: pass by value #[inline(always)] pub fn contains(&self, offset: TextUnit) -> bool { self.start() <= offset && offset < self.end() } + // BREAK: pass by value #[inline(always)] pub fn contains_inclusive(&self, offset: TextUnit) -> bool { self.start() <= offset && offset <= self.end() From c06f0046b7daf2343d3d92fca385ae051b803d0f Mon Sep 17 00:00:00 2001 From: Edwin Cheng Date: Tue, 25 Feb 2020 22:10:21 +0800 Subject: [PATCH 13/87] Add convex_hull for TextRange --- src/lib.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index c00954c55c..2ca54859fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -299,6 +299,15 @@ impl TextRange { } } + // BREAK: pass by value + #[inline(always)] + /// The smallest convex set that contains both ranges + pub fn convex_hull(&self, other: &TextRange) -> TextRange { + let start = self.start().min(other.start()); + let end = self.end().max(other.end()); + TextRange::from_to(start, end) + } + // BREAK: pass by value #[inline(always)] pub fn contains(&self, offset: TextUnit) -> bool { @@ -433,6 +442,13 @@ mod tests { assert_eq!(r(1, 2).intersection(&r(3, 4)), None); } + #[test] + fn check_convex_hull() { + assert_eq!(r(1, 2).convex_hull(&r(2, 3)), r(1, 3)); + assert_eq!(r(1, 5).convex_hull(&r(2, 3)), r(1, 5)); + assert_eq!(r(1, 2).convex_hull(&r(4, 5)), r(1, 5)); + } + #[test] fn check_contains() { assert!(!r(1, 3).contains(0.into())); From 8d0d3e9b0d8281396a4c926e1cf76025a6fff2b1 Mon Sep 17 00:00:00 2001 From: Edwin Cheng Date: Tue, 25 Feb 2020 22:52:04 +0800 Subject: [PATCH 14/87] Rename convex_hull to extend_to --- src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2ca54859fe..bd8e820e29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -301,8 +301,8 @@ impl TextRange { // BREAK: pass by value #[inline(always)] - /// The smallest convex set that contains both ranges - pub fn convex_hull(&self, other: &TextRange) -> TextRange { + /// The smallest range that contains both ranges + pub fn extend_to(&self, other: &TextRange) -> TextRange { let start = self.start().min(other.start()); let end = self.end().max(other.end()); TextRange::from_to(start, end) @@ -443,10 +443,10 @@ mod tests { } #[test] - fn check_convex_hull() { - assert_eq!(r(1, 2).convex_hull(&r(2, 3)), r(1, 3)); - assert_eq!(r(1, 5).convex_hull(&r(2, 3)), r(1, 5)); - assert_eq!(r(1, 2).convex_hull(&r(4, 5)), r(1, 5)); + fn check_extend_to() { + assert_eq!(r(1, 2).extend_to(&r(2, 3)), r(1, 3)); + assert_eq!(r(1, 5).extend_to(&r(2, 3)), r(1, 5)); + assert_eq!(r(1, 2).extend_to(&r(4, 5)), r(1, 5)); } #[test] From af901ad23af20fb8b32440e610d23bc34e2901b0 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 26 Feb 2020 10:58:36 +0100 Subject: [PATCH 15/87] Publish v0.1.10 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 40afa4311c..9080b3fad8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "text_unit" -version = "0.1.9" +version = "0.1.10" authors = ["Aleksey Kladov "] description = "Newtypes for text offsets" license = "MIT OR Apache-2.0" From 5ee8fea628212aa416172f4dcb2bb37b42047703 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Sat, 7 Mar 2020 17:10:37 -0500 Subject: [PATCH 16/87] Aggressive refactor --- Cargo.toml | 21 +- src/lib.rs | 468 +-------------------------------------------- src/range.rs | 355 ++++++++++++++++++++++++++++++++++ src/serde_impls.rs | 40 ++++ src/size.rs | 246 ++++++++++++++++++++++++ src/traits.rs | 29 +++ tests/main.rs | 67 +++++++ tests/serde.rs | 49 +++++ 8 files changed, 812 insertions(+), 463 deletions(-) create mode 100644 src/range.rs create mode 100644 src/serde_impls.rs create mode 100644 src/size.rs create mode 100644 src/traits.rs create mode 100644 tests/main.rs create mode 100644 tests/serde.rs diff --git a/Cargo.toml b/Cargo.toml index 9080b3fad8..b5d42f0182 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,25 @@ [package] -name = "text_unit" -version = "0.1.10" -authors = ["Aleksey Kladov "] +name = "text-size" +version = "0.99.0-dev.1" +edition = "2018" + +authors = [ + "Aleksey Kladov ", + "Christopher Durham (CAD97) " +] description = "Newtypes for text offsets" license = "MIT OR Apache-2.0" repository = "https://github.com/matklad/text_unit" documentation = "https://docs.rs/text_unit" [dependencies] -serde = { version = "1", optional = true, default_features = false } +serde = { version = "1.0", optional = true, default_features = false } deepsize = { version = "0.1", optional = true, default_features = false } + +[dev-dependencies] +serde_test = "1.0" + +[[test]] +name = "serde" +path = "tests/serde.rs" +required-features = ["serde"] diff --git a/src/lib.rs b/src/lib.rs index bd8e820e29..66bc653790 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,466 +1,16 @@ -#[cfg(feature = "serde")] -extern crate serde; +//! Newtypes for working with text sizes/ranges in a more type-safe manner. -use std::{fmt, iter, ops}; +#![forbid(unsafe_code)] +#![warn(missing_debug_implementations, missing_docs)] -/// An offset into text. -/// Offset is represented as `u32` storing number of utf8-bytes, -/// but most of the clients should treat it like opaque measure. -// BREAK: TextSize(u32) -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] -pub struct TextUnit(u32); - -impl TextUnit { - // BREAK: consider renaming? - /// `TextUnit` equal to the length of this char. - #[inline(always)] - pub fn of_char(c: char) -> TextUnit { - TextUnit(c.len_utf8() as u32) - } - - // BREAK: consider renaming? - /// `TextUnit` equal to the length of this string. - /// - /// # Panics - /// Panics if the length of the string is greater than `u32::max_value()` - #[inline(always)] - pub fn of_str(s: &str) -> TextUnit { - if s.len() > u32::max_value() as usize { - panic!("string is to long") - } - TextUnit(s.len() as u32) - } - - #[inline(always)] - pub fn checked_sub(self, other: TextUnit) -> Option { - self.0.checked_sub(other.0).map(TextUnit) - } - - #[inline(always)] - pub fn from_usize(size: usize) -> TextUnit { - #[cfg(debug_assertions)] - { - if size > u32::max_value() as usize { - panic!("overflow when converting to TextUnit: {}", size) - } - } - (size as u32).into() - } - - #[inline(always)] - pub fn to_usize(self) -> usize { - u32::from(self) as usize - } -} - -impl fmt::Debug for TextUnit { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ::fmt(self, f) - } -} - -impl fmt::Display for TextUnit { - #[inline(always)] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -impl From for u32 { - #[inline(always)] - fn from(tu: TextUnit) -> u32 { - tu.0 - } -} - -impl From for TextUnit { - #[inline(always)] - fn from(tu: u32) -> TextUnit { - TextUnit(tu) - } -} - -macro_rules! unit_ops_impls { - ($T:ident, $f:ident, $op:tt, $AT:ident, $af:ident) => { - -impl ops::$T for TextUnit { - type Output = TextUnit; - #[inline(always)] - fn $f(self, rhs: TextUnit) -> TextUnit { - TextUnit(self.0 $op rhs.0) - } -} - -impl<'a> ops::$T<&'a TextUnit> for TextUnit { - type Output = TextUnit; - #[inline(always)] - fn $f(self, rhs: &'a TextUnit) -> TextUnit { - ops::$T::$f(self, *rhs) - } -} - -impl<'a> ops::$T for &'a TextUnit { - type Output = TextUnit; - #[inline(always)] - fn $f(self, rhs: TextUnit) -> TextUnit { - ops::$T::$f(*self, rhs) - } -} - -impl<'a, 'b> ops::$T<&'a TextUnit> for &'b TextUnit { - type Output = TextUnit; - #[inline(always)] - fn $f(self, rhs: &'a TextUnit) -> TextUnit { - ops::$T::$f(*self, *rhs) - } -} - -impl ops::$AT for TextUnit { - #[inline(always)] - fn $af(&mut self, rhs: TextUnit) { - self.0 = self.0 $op rhs.0 - } -} - -impl<'a> ops::$AT<&'a TextUnit> for TextUnit { - #[inline(always)] - fn $af(&mut self, rhs: &'a TextUnit) { - ops::$AT::$af(self, *rhs) - } -} - }; -} - -macro_rules! range_ops_impls { - ($T:ident, $f:ident, $op:tt, $AT:ident, $af:ident) => { - -impl ops::$T for TextRange { - type Output = TextRange; - #[inline(always)] - fn $f(self, rhs: TextUnit) -> TextRange { - TextRange::from_to( - self.start() $op rhs, - self.end() $op rhs, - ) - } -} - -impl<'a> ops::$T<&'a TextUnit> for TextRange { - type Output = TextRange; - #[inline(always)] - fn $f(self, rhs: &'a TextUnit) -> TextRange { - TextRange::from_to( - self.start() $op rhs, - self.end() $op rhs, - ) - } -} - -impl<'a> ops::$T for &'a TextRange { - type Output = TextRange; - #[inline(always)] - fn $f(self, rhs: TextUnit) -> TextRange { - TextRange::from_to( - self.start() $op rhs, - self.end() $op rhs, - ) - } -} - -impl<'a, 'b> ops::$T<&'a TextUnit> for &'b TextRange { - type Output = TextRange; - #[inline(always)] - fn $f(self, rhs: &'a TextUnit) -> TextRange { - TextRange::from_to( - self.start() $op rhs, - self.end() $op rhs, - ) - } -} - -impl ops::$AT for TextRange { - #[inline(always)] - fn $af(&mut self, rhs: TextUnit) { - *self = *self $op rhs - } -} - -impl<'a> ops::$AT<&'a TextUnit> for TextRange { - #[inline(always)] - fn $af(&mut self, rhs: &'a TextUnit) { - *self = *self $op rhs - } -} - }; -} - -unit_ops_impls!(Add, add, +, AddAssign, add_assign); -unit_ops_impls!(Sub, sub, -, SubAssign, sub_assign); -range_ops_impls!(Add, add, +, AddAssign, add_assign); -range_ops_impls!(Sub, sub, -, SubAssign, sub_assign); - -impl<'a> iter::Sum<&'a TextUnit> for TextUnit { - fn sum>(iter: I) -> TextUnit { - iter.fold(TextUnit::from(0), ops::Add::add) - } -} - -impl iter::Sum for TextUnit { - fn sum>(iter: I) -> TextUnit { - iter.fold(TextUnit::from(0), ops::Add::add) - } -} - -/// A range in the text, represented as a pair of `TextUnit`s. -/// -/// # Panics -/// Slicing a `&str` with `TextRange` panics if the result is -/// not a valid utf8 string. -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub struct TextRange { - start: TextUnit, - end: TextUnit, -} - -impl fmt::Debug for TextRange { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ::fmt(self, f) - } -} - -impl fmt::Display for TextRange { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "[{}; {})", self.start(), self.end()) - } -} - -impl TextRange { - // BREAK: TextRange::new(from..to)? - // BREAK: TextRange(from, to)? - /// The left-inclusive range (`[from..to)`) between to points in the text - #[inline(always)] - pub fn from_to(from: TextUnit, to: TextUnit) -> TextRange { - assert!(from <= to, "Invalid text range [{}; {})", from, to); - TextRange { - start: from, - end: to, - } - } - - /// The left-inclusive range (`[offset..offset + len)`) between to points in the text - #[inline(always)] - pub fn offset_len(offset: TextUnit, len: TextUnit) -> TextRange { - TextRange::from_to(offset, offset + len) - } - - // BREAK: pass by value - /// The inclusive start of this range - #[inline(always)] - pub fn start(&self) -> TextUnit { - self.start - } - - // BREAK: pass by value - /// The exclusive end of this range - #[inline(always)] - pub fn end(&self) -> TextUnit { - self.end - } - - // BREAK: pass by value - /// The length of this range - #[inline(always)] - pub fn len(&self) -> TextUnit { - self.end - self.start - } - - // BREAK: pass by value - /// Is this range empty of any content? - #[inline(always)] - pub fn is_empty(&self) -> bool { - self.start() == self.end() - } - - // BREAK: pass by value - #[inline(always)] - pub fn is_subrange(&self, other: &TextRange) -> bool { - other.start() <= self.start() && self.end() <= other.end() - } - - // BREAK: pass by value - #[inline(always)] - pub fn intersection(&self, other: &TextRange) -> Option { - let start = self.start.max(other.start()); - let end = self.end.min(other.end()); - if start <= end { - Some(TextRange::from_to(start, end)) - } else { - None - } - } - - // BREAK: pass by value - #[inline(always)] - /// The smallest range that contains both ranges - pub fn extend_to(&self, other: &TextRange) -> TextRange { - let start = self.start().min(other.start()); - let end = self.end().max(other.end()); - TextRange::from_to(start, end) - } - - // BREAK: pass by value - #[inline(always)] - pub fn contains(&self, offset: TextUnit) -> bool { - self.start() <= offset && offset < self.end() - } - - // BREAK: pass by value - #[inline(always)] - pub fn contains_inclusive(&self, offset: TextUnit) -> bool { - self.start() <= offset && offset <= self.end() - } - - #[inline(always)] - pub fn checked_sub(self, other: TextUnit) -> Option { - let res = TextRange::offset_len( - self.start().checked_sub(other)?, - self.len() - ); - Some(res) - } -} - -impl ops::RangeBounds for TextRange { - fn start_bound(&self) -> ops::Bound<&TextUnit> { - ops::Bound::Included(&self.start) - } - - fn end_bound(&self) -> ops::Bound<&TextUnit> { - ops::Bound::Excluded(&self.end) - } -} - -impl ops::Index for str { - type Output = str; - - fn index(&self, index: TextRange) -> &str { - &self[index.start().0 as usize..index.end().0 as usize] - } -} - -impl ops::Index for String { - type Output = str; - - fn index(&self, index: TextRange) -> &str { - &self.as_str()[index] - } -} +mod range; +mod size; +mod traits; #[cfg(feature = "serde")] -mod serde_impls { - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - use {TextRange, TextUnit}; +mod serde_impls; - impl Serialize for TextUnit { - fn serialize(&self, serializer: S) -> Result { - self.0.serialize(serializer) - } - } - - impl<'de> Deserialize<'de> for TextUnit { - fn deserialize>(deserializer: D) -> Result { - let value = Deserialize::deserialize(deserializer)?; - Ok(TextUnit(value)) - } - } - - impl Serialize for TextRange { - fn serialize(&self, serializer: S) -> Result { - (self.start, self.end).serialize(serializer) - } - } - - impl<'de> Deserialize<'de> for TextRange { - fn deserialize>(deserializer: D) -> Result { - let (start, end) = Deserialize::deserialize(deserializer)?; - Ok(TextRange { start, end }) - } - } -} +pub use crate::{range::TextRange, size::TextSize, traits::TextSized}; #[cfg(feature = "deepsize")] -mod deepsize_impls { - deepsize::known_deep_size!(0, crate::TextUnit, crate::TextRange); -} - -#[cfg(test)] -mod tests { - use super::*; - - fn r(from: u32, to: u32) -> TextRange { - TextRange::from_to(from.into(), to.into()) - } - - #[test] - fn test_sum() { - let xs: Vec = vec![0.into(), 1.into(), 2.into()]; - assert_eq!(xs.iter().sum::(), 3.into()); - assert_eq!(xs.into_iter().sum::(), 3.into()); - } - - #[test] - fn test_ops() { - let range = r(10, 20); - let u: TextUnit = 5.into(); - assert_eq!(range + u, r(15, 25)); - assert_eq!(range - u, r(5, 15)); - } - - #[test] - fn test_checked_ops() { - let x: TextUnit = 1.into(); - assert_eq!(x.checked_sub(1.into()), Some(0.into())); - assert_eq!(x.checked_sub(2.into()), None); - - assert_eq!(r(1, 2).checked_sub(1.into()), Some(r(0, 1))); - assert_eq!(x.checked_sub(2.into()), None); - } - - #[test] - fn test_subrange() { - let r1 = r(2, 4); - let r2 = r(2, 3); - let r3 = r(1, 3); - assert!(r2.is_subrange(&r1)); - assert!(!r3.is_subrange(&r1)); - } - - #[test] - fn check_intersection() { - assert_eq!(r(1, 2).intersection(&r(2, 3)), Some(r(2, 2))); - assert_eq!(r(1, 5).intersection(&r(2, 3)), Some(r(2, 3))); - assert_eq!(r(1, 2).intersection(&r(3, 4)), None); - } - - #[test] - fn check_extend_to() { - assert_eq!(r(1, 2).extend_to(&r(2, 3)), r(1, 3)); - assert_eq!(r(1, 5).extend_to(&r(2, 3)), r(1, 5)); - assert_eq!(r(1, 2).extend_to(&r(4, 5)), r(1, 5)); - } - - #[test] - fn check_contains() { - assert!(!r(1, 3).contains(0.into())); - assert!(r(1, 3).contains(1.into())); - assert!(r(1, 3).contains(2.into())); - assert!(!r(1, 3).contains(3.into())); - assert!(!r(1, 3).contains(4.into())); - - assert!(!r(1, 3).contains_inclusive(0.into())); - assert!(r(1, 3).contains_inclusive(1.into())); - assert!(r(1, 3).contains_inclusive(2.into())); - assert!(r(1, 3).contains_inclusive(3.into())); - assert!(!r(1, 3).contains_inclusive(4.into())); - } -} +deepsize::known_deep_size!(0, TextSize, TextRange); diff --git a/src/range.rs b/src/range.rs new file mode 100644 index 0000000000..89012f2779 --- /dev/null +++ b/src/range.rs @@ -0,0 +1,355 @@ +use { + crate::{TextSize, TextSized}, + std::{ + cmp, + convert::{TryFrom, TryInto}, + fmt, + num::TryFromIntError, + ops::{ + Add, AddAssign, Bound, Index, IndexMut, Range, RangeBounds, RangeInclusive, RangeTo, + RangeToInclusive, Sub, SubAssign, + }, + }, +}; + +/// A range in text, represented as a pair of [`TextSize`][struct@TextSize]. +/// +/// It is a logical error to have `end() < start()`, but +/// code must not assume this is true for `unsafe` guarantees. +/// +/// # Translation from `text_unit` +/// +/// - `TextRange::from_to(from, to)` ⟹ `TextRange::from(from..to)` +/// - `TextRange::offset_len(offset, size)` ⟹ `TextRange::at(offset).with_len(size)` +/// - `range.start()` ⟹ `range.start()` +/// - `range.end()` ⟹ `range.end()` +/// - `range.len()` ⟹ `range.len()` +/// - `range.is_empty()` ⟹ `range.is_empty()` +/// - `a.is_subrange(b)` ⟹ `b.contains(a)` +/// - `a.intersection(b)` ⟹ `TextRange::intersection(a, b)` +/// - `a.extend_to(b)` ⟹ `TextRange::covering(a, b)` +/// - `range.contains(offset)` ⟹ `range.contains_point(point)` +/// - `range.contains_inclusive(offset)` ⟹ `range.contains_point_inclusive(point)` +/// +/// † See the note on [`TextRange::len`] for differing behavior for incorrect reverse ranges. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct TextRange { + start: TextSize, + end: TextSize, +} + +#[allow(non_snake_case)] +pub(crate) const fn TextRange(start: TextSize, end: TextSize) -> TextRange { + TextRange { start, end } +} + +impl fmt::Debug for TextRange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl fmt::Display for TextRange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[{}..{})", self.start(), self.end()) + } +} + +/// Identity methods. +impl TextRange { + /// The start point of this range. + pub const fn start(self) -> TextSize { + self.start + } + + /// The end point of this range. + pub const fn end(self) -> TextSize { + self.end + } + + /// The size of this range. + /// + /// # Panics + /// + /// When `end() < start()`, triggers a subtraction overflow. + /// This will panic with debug assertions, and overflow without. + pub const fn len(self) -> TextSize { + // HACK for const fn: math on primitives only + TextSize(self.end().raw - self.start().raw) + } + + /// Check if this range empty or reversed. + /// + /// When `end() < start()`, this returns false. + /// Code should prefer `is_empty()` to `len() == 0`, + /// as this safeguards against incorrect reverse ranges. + pub const fn is_empty(self) -> bool { + // HACK for const fn: math on primitives only + self.start().raw >= self.end().raw + } +} + +/// Manipulation methods. +impl TextRange { + /// A range covering the text size of some text-like object. + pub fn of(size: impl TextSized) -> TextRange { + TextRange(0.into(), size.text_size()) + } + + /// An empty range at some text size offset. + pub fn at(size: impl Into) -> TextRange { + let size = size.into(); + TextRange(size, size) + } + + /// Set the length without changing the starting offset. + pub fn with_len(self, len: impl Into) -> TextRange { + TextRange(self.start(), self.start() + len.into()) + } + + /// Set the starting offset without changing the length. + pub fn with_offset(self, offset: impl Into) -> TextRange { + TextRange::at(offset).with_len(self.len()) + } + + /// Check if this range completely contains another range. + pub fn contains(self, other: TextRange) -> bool { + self.start() <= other.start() && other.end() <= self.end() + } + + /// The range covered by both ranges, if it exists. + /// If the ranges touch but do not overlap, the output range is empty. + pub fn intersection(lhs: TextRange, rhs: TextRange) -> Option { + let start = cmp::max(lhs.start(), rhs.start()); + let end = cmp::min(lhs.end(), rhs.end()); + Some(TextRange(start, end)).filter(|_| start <= end) + } + + /// The smallest range that completely contains both ranges. + pub fn covering(lhs: TextRange, rhs: TextRange) -> TextRange { + let start = cmp::min(lhs.start(), rhs.start()); + let end = cmp::max(lhs.end(), rhs.end()); + TextRange(start, end) + } + + /// Check if this range contains a point. + /// + /// The end index is considered excluded. + pub fn contains_point(self, point: impl Into) -> bool { + let point = point.into(); + self.start() <= point && point < self.end() + } + + /// Check if this range contains a point. + /// + /// The end index is considered included. + pub fn contains_point_inclusive(self, point: impl Into) -> bool { + let point = point.into(); + self.start() <= point && point <= self.end() + } + + /// Offset the entire range by some text size. + pub fn checked_add(self, rhs: impl TryInto) -> Option { + let rhs = rhs.try_into().ok()?; + Some(TextRange( + self.start().checked_add(rhs)?, + self.end().checked_add(rhs)?, + )) + } + + /// Offset the entire range by some text size. + pub fn checked_sub(self, rhs: impl TryInto) -> Option { + let rhs = rhs.try_into().ok()?; + Some(TextRange( + self.start().checked_sub(rhs)?, + self.end().checked_sub(rhs)?, + )) + } +} + +impl Index for str { + type Output = str; + fn index(&self, index: TextRange) -> &Self::Output { + &self[index.start().ix()..index.end().ix()] + } +} + +impl IndexMut for str { + fn index_mut(&mut self, index: TextRange) -> &mut Self::Output { + &mut self[index.start().ix()..index.end().ix()] + } +} + +impl RangeBounds for TextRange { + fn start_bound(&self) -> Bound<&TextSize> { + Bound::Included(&self.start) + } + + fn end_bound(&self) -> Bound<&TextSize> { + Bound::Excluded(&self.end) + } +} + +macro_rules! conversions { + (From<$lte:ident> for TextRange) => { + impl From> for TextRange { + fn from(value: Range<$lte>) -> TextRange { + TextRange(value.start.into(), value.end.into()) + } + } + impl TryFrom> for TextRange { + type Error = TryFromIntError; + fn try_from(value: RangeInclusive<$lte>) -> Result { + let (start, end) = value.into_inner(); + let end: TextSize = end.into(); + // This is the only way to get a TryFromIntError currently. + let end = end.checked_add(1).ok_or_else(|| u8::try_from(-1).unwrap_err())?; + Ok(TextRange(start.into(), end)) + } + } + impl From> for TextRange { + fn from(value: RangeTo<$lte>) -> TextRange { + TextRange(0.into(), value.end.into()) + } + } + impl TryFrom> for TextRange { + type Error = TryFromIntError; + fn try_from(value: RangeToInclusive<$lte>) -> Result { + let start: TextSize = 0.into(); + let end: TextSize = value.end.into(); + TextRange::try_from(start..=end) + } + } + }; + (TryFrom<$gt:ident> for TextRange) => { + impl TryFrom> for TextRange { + type Error = <$gt as TryInto>::Error; + fn try_from(value: Range<$gt>) -> Result { + Ok(TextRange(value.start.try_into()?, value.end.try_into()?)) + } + } + impl TryFrom> for TextRange { + type Error = TryFromIntError; + fn try_from(value: RangeInclusive<$gt>) -> Result { + let (start, end) = value.into_inner(); + let end: TextSize = end.try_into()?; + // This is the only way to get a TryFromIntError currently. + let end = end.checked_add(1).ok_or_else(|| u8::try_from(-1).unwrap_err())?; + Ok(TextRange(start.try_into()?, end)) + } + } + impl TryFrom> for TextRange { + type Error = TryFromIntError; + fn try_from(value: RangeTo<$gt>) -> Result { + Ok(TextRange(0.into(), value.end.try_into()?)) + } + } + impl TryFrom> for TextRange { + type Error = TryFromIntError; + fn try_from(value: RangeToInclusive<$gt>) -> Result { + let start: TextSize = 0.into(); + let end: TextSize = value.end.try_into()?; + TextRange::try_from(start..=end) + } + } + }; + { + lt TextSize [$($lt:ident)*] + eq TextSize [$($eq:ident)*] + gt TextSize [$($gt:ident)*] + varries [$($var:ident)*] + } => { + $( + // Not `From` yet because of integer type fallback. We want e.g. + // `TextRange::from(0)` and `range + 1` to work, and more `From` + // impls means that this will try (and fail) to use i32 rather + // than one of the unsigned integer types that actually work. + conversions!(TryFrom<$lt> for TextRange); + )* + + $( + conversions!(From<$eq> for TextRange); + )* + + $( + conversions!(TryFrom<$gt> for TextRange); + )* + + $( + conversions!(TryFrom<$var> for TextRange); + )* + }; +} + +// FIXME: when `default impl` is usable, change to blanket impls for [Try]Into instead +conversions! { + lt TextSize [u8 u16] + eq TextSize [u32 TextSize] + gt TextSize [u64] + varries [usize] +} + +impl Into for &'_ TextRange { + fn into(self) -> TextRange { + *self + } +} + +impl Into for &'_ mut TextRange { + fn into(self) -> TextRange { + *self + } +} + +macro_rules! op { + (impl $Op:ident for TextRange by fn $f:ident = $op:tt) => { + impl $Op for TextRange + where + TextSize: $Op, + { + type Output = TextRange; + fn $f(self, rhs: IntoSize) -> TextRange { + TextRange(self.start() $op rhs, self.end() $op rhs) + } + } + impl $Op for &'_ TextRange + where + TextRange: $Op, + { + type Output = TextRange; + fn $f(self, rhs: IntoSize) -> TextRange { + *self $op rhs + } + } + impl $Op for &'_ mut TextRange + where + TextRange: $Op, + { + type Output = TextRange; + fn $f(self, rhs: IntoSize) -> TextRange { + *self $op rhs + } + } + }; +} + +op!(impl Add for TextRange by fn add = +); +op!(impl Sub for TextRange by fn sub = -); + +impl AddAssign for TextRange +where + TextRange: Add, +{ + fn add_assign(&mut self, rhs: A) { + *self = *self + rhs + } +} + +impl SubAssign for TextRange +where + TextRange: Sub, +{ + fn sub_assign(&mut self, rhs: S) { + *self = *self - rhs + } +} diff --git a/src/serde_impls.rs b/src/serde_impls.rs new file mode 100644 index 0000000000..1963413fd8 --- /dev/null +++ b/src/serde_impls.rs @@ -0,0 +1,40 @@ +use { + crate::{TextRange, TextSize}, + serde::{Deserialize, Deserializer, Serialize, Serializer}, +}; + +impl Serialize for TextSize { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.raw.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for TextSize { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Deserialize::deserialize(deserializer).map(TextSize) + } +} + +impl Serialize for TextRange { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + (self.start(), self.end()).serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for TextRange { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Deserialize::deserialize(deserializer).map(|(start, end)| TextRange(start, end)) + } +} diff --git a/src/size.rs b/src/size.rs new file mode 100644 index 0000000000..80dba0aba6 --- /dev/null +++ b/src/size.rs @@ -0,0 +1,246 @@ +use { + crate::TextSized, + std::{ + convert::{TryFrom, TryInto}, + fmt, iter, + num::TryFromIntError, + ops::{Add, AddAssign, Sub, SubAssign}, + u32, + }, +}; + +/// A measure of text length. Also, equivalently, an index into text. +/// +/// This is a utf8-bytes-offset stored as `u32`, but +/// most clients should treat it as an opaque measure. +/// +/// # Translation from `text_unit` +/// +/// - `TextUnit::of_char(c)` ⟹ `TextSize::of(c)` +/// - `TextUnit::of_str(s)` ⟹ `TextSize:of(s)` +/// - `TextUnit::from_usize(size)` ⟹ `TextSize::new(size)` +/// - `unit.to_usize()` ⟹ `size.ix()` +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TextSize { + pub(crate) raw: u32, +} + +#[allow(non_snake_case)] +pub(crate) const fn TextSize(raw: u32) -> TextSize { + TextSize { raw } +} + +impl fmt::Debug for TextSize { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl fmt::Display for TextSize { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.raw, f) + } +} + +impl TextSize { + /// The text size of some text-like object. + pub fn of(text: &impl TextSized) -> TextSize { + text.text_size() + } + + /// A text size for some `usize`. + /// + /// # Panics + /// + /// Panics if the size is greater than `u32::MAX` and debug assertions are + /// enabled. If debug assertions are not enabled, wraps into `u32` space. + pub fn new(size: usize) -> TextSize { + if let Ok(size) = size.try_into() { + size + } else if cfg!(debug_assertions) { + panic!("overflow when converting to TextSize"); + } else { + TextSize(size as u32) + } + } + + /// Convert this text size into the standard indexing type. + /// + /// # Panics + /// + /// Panics if the size is greater than `usize::MAX`. This can only + /// occur on targets where `size_of::() < size_of::()`. + pub fn ix(self) -> usize { + if let Ok(ix) = self.try_into() { + ix + } else { + panic!("overflow when converting TextSize to usize index") + } + } +} + +/// Methods to act like a primitive integer type, where reasonably applicable. +// Last updated for parity with Rust 1.42.0. +impl TextSize { + /// The smallest representable text size. (`u32::MIN`) + pub const MIN: TextSize = TextSize(u32::MIN); + /// The largest representable text size. (`u32::MAX`) + pub const MAX: TextSize = TextSize(u32::MAX); + + #[allow(missing_docs)] + pub fn checked_add(self, rhs: impl TryInto) -> Option { + let rhs = rhs.try_into().ok()?; + self.raw.checked_add(rhs.raw).map(TextSize) + } + + #[allow(missing_docs)] + pub fn checked_sub(self, rhs: impl TryInto) -> Option { + let rhs = rhs.try_into().ok()?; + self.raw.checked_sub(rhs.raw).map(TextSize) + } +} + +macro_rules! conversions { + (From for $gte:ident) => { + impl From for $gte { + fn from(value: TextSize) -> $gte { + value.raw.into() + } + } + }; + (From<$lte:ident> for TextSize) => { + impl From<$lte> for TextSize { + fn from(value: $lte) -> TextSize { + TextSize(value.into()) + } + } + }; + (TryFrom for $lt:ident) => { + impl TryFrom for $lt { + type Error = TryFromIntError; + fn try_from(value: TextSize) -> Result<$lt, Self::Error> { + value.raw.try_into() + } + } + }; + (TryFrom<$gt:ident> for TextSize) => { + impl TryFrom<$gt> for TextSize { + type Error = <$gt as TryInto>::Error; + fn try_from(value: $gt) -> Result { + value.try_into().map(TextSize) + } + } + }; + { + lt u32 [$($lt:ident)*] + eq u32 [$($eq:ident)*] + gt u32 [$($gt:ident)*] + varries [$($var:ident)*] + } => { + $( + // Not `From` yet because of integer type fallback. We want e.g. + // `TextSize::from(0)` and `size + 1` to work, and more `From` + // impls means that this will try (and fail) to use i32 rather + // than one of the unsigned integer types that actually work. + conversions!(TryFrom<$lt> for TextSize); + conversions!(TryFrom for $lt); + )* + + $( + conversions!(From<$eq> for TextSize); + conversions!(From for $eq); + )* + + $( + conversions!(TryFrom<$gt> for TextSize); + conversions!(From for $gt); + )* + + $( + conversions!(TryFrom<$var> for TextSize); + conversions!(TryFrom for $var); + )* + }; +} + +conversions! { + lt u32 [u8 u16] + eq u32 [u32] + gt u32 [u64] + varries [usize i32] // i32 so that `checked_add($lit)` (`try_from($lit)`) can work + // this will unfortunately have to hang around even if integer literal type fallback improves +} + +impl Into for &'_ TextSize { + fn into(self) -> TextSize { + *self + } +} + +impl Into for &'_ mut TextSize { + fn into(self) -> TextSize { + *self + } +} + +macro_rules! op { + (impl $Op:ident for TextSize by fn $f:ident = $op:tt) => { + impl> $Op for TextSize { + type Output = TextSize; + fn $f(self, rhs: IntoSize) -> TextSize { + TextSize(self.raw $op rhs.into().raw) + } + } + impl $Op for &'_ TextSize + where + TextSize: $Op, + { + type Output = TextSize; + fn $f(self, rhs: IntoSize) -> TextSize { + *self $op rhs + } + } + impl $Op for &'_ mut TextSize + where + TextSize: $Op, + { + type Output = TextSize; + fn $f(self, rhs: IntoSize) -> TextSize { + *self $op rhs + } + } + }; +} + +op!(impl Add for TextSize by fn add = +); +op!(impl Sub for TextSize by fn sub = -); + +impl AddAssign for TextSize +where + TextSize: Add, +{ + fn add_assign(&mut self, rhs: A) { + *self = *self + rhs + } +} + +impl SubAssign for TextSize +where + TextSize: Sub, +{ + fn sub_assign(&mut self, rhs: S) { + *self = *self - rhs + } +} + +impl iter::Sum for TextSize { + fn sum>(iter: I) -> TextSize { + iter.fold(TextSize::default(), Add::add) + } +} + +impl<'a> iter::Sum<&'a Self> for TextSize { + fn sum>(iter: I) -> Self { + iter.fold(TextSize::default(), Add::add) + } +} diff --git a/src/traits.rs b/src/traits.rs new file mode 100644 index 0000000000..52601534d2 --- /dev/null +++ b/src/traits.rs @@ -0,0 +1,29 @@ +use { + crate::{TextRange, TextSize}, + std::convert::TryInto, +}; + +/// Text-like structures that have a text size. +pub trait TextSized { + /// The size of this text-alike. + fn text_size(&self) -> TextSize; +} + +impl TextSized for str { + fn text_size(&self) -> TextSize { + let len = self.len(); + TextSize::new(len) + } +} + +impl TextSized for char { + fn text_size(&self) -> TextSize { + self.len_utf8().try_into().unwrap() + } +} + +impl TextSized for TextRange { + fn text_size(&self) -> TextSize { + self.len() + } +} diff --git a/tests/main.rs b/tests/main.rs new file mode 100644 index 0000000000..a7eef0a2cd --- /dev/null +++ b/tests/main.rs @@ -0,0 +1,67 @@ +use text_size::*; + +fn r(from: u32, to: u32) -> TextRange { + TextRange::from(from..to) +} + +#[test] +fn sum() { + let xs: Vec = vec![0.into(), 1.into(), 2.into()]; + assert_eq!(xs.iter().sum::(), 3.into()); + assert_eq!(xs.into_iter().sum::(), 3.into()); +} + +#[test] +fn math() { + let range = r(10, 20); + assert_eq!(range + 5, r(15, 25)); + assert_eq!(range - 5, r(5, 15)); +} + +#[test] +fn checked_math() { + let x: TextSize = 1.into(); + assert_eq!(x.checked_sub(1), Some(0.into())); + assert_eq!(x.checked_sub(2), None); + + assert_eq!(r(1, 2).checked_sub(1), Some(r(0, 1))); + assert_eq!(x.checked_sub(2), None); +} + +#[test] +fn contains() { + let r1 = r(2, 4); + let r2 = r(2, 3); + let r3 = r(1, 3); + assert!(r1.contains(r2)); + assert!(!r1.contains(r3)); +} + +#[test] +fn intersection() { + assert_eq!(TextRange::intersection(r(1, 2), r(2, 3)), Some(r(2, 2))); + assert_eq!(TextRange::intersection(r(1, 5), r(2, 3)), Some(r(2, 3))); + assert_eq!(TextRange::intersection(r(1, 2), r(3, 4)), None); +} + +#[test] +fn covering() { + assert_eq!(TextRange::covering(r(1, 2), r(2, 3)), r(1, 3)); + assert_eq!(TextRange::covering(r(1, 5), r(2, 3)), r(1, 5)); + assert_eq!(TextRange::covering(r(1, 2), r(4, 5)), r(1, 5)); +} + +#[test] +fn contains_point() { + assert!(!r(1, 3).contains_point(0)); + assert!(r(1, 3).contains_point(1)); + assert!(r(1, 3).contains_point(2)); + assert!(!r(1, 3).contains_point(3)); + assert!(!r(1, 3).contains_point(4)); + + assert!(!r(1, 3).contains_point_inclusive(0)); + assert!(r(1, 3).contains_point_inclusive(1)); + assert!(r(1, 3).contains_point_inclusive(2)); + assert!(r(1, 3).contains_point_inclusive(3)); + assert!(!r(1, 3).contains_point_inclusive(4)); +} diff --git a/tests/serde.rs b/tests/serde.rs new file mode 100644 index 0000000000..439b9d71f5 --- /dev/null +++ b/tests/serde.rs @@ -0,0 +1,49 @@ +use {serde_test::*, text_size::*}; + +#[test] +fn size() { + assert_tokens(&TextSize::new(00), &[Token::U32(00)]); + assert_tokens(&TextSize::new(10), &[Token::U32(10)]); + assert_tokens(&TextSize::new(20), &[Token::U32(20)]); + assert_tokens(&TextSize::new(30), &[Token::U32(30)]); +} + +#[test] +fn range() { + assert_tokens( + &TextRange::from(00..10), + &[ + Token::Tuple { len: 2 }, + Token::U32(00), + Token::U32(10), + Token::TupleEnd, + ], + ); + assert_tokens( + &TextRange::from(10..20), + &[ + Token::Tuple { len: 2 }, + Token::U32(10), + Token::U32(20), + Token::TupleEnd, + ], + ); + assert_tokens( + &TextRange::from(20..30), + &[ + Token::Tuple { len: 2 }, + Token::U32(20), + Token::U32(30), + Token::TupleEnd, + ], + ); + assert_tokens( + &TextRange::from(30..40), + &[ + Token::Tuple { len: 2 }, + Token::U32(30), + Token::U32(40), + Token::TupleEnd, + ], + ); +} From 7a1373e6624c2eca901e09f02aee5c1224c0b6c3 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Sat, 7 Mar 2020 22:33:21 -0500 Subject: [PATCH 17/87] Scale back the exposed API surface --- Cargo.toml | 3 +- src/lib.rs | 3 - src/range.rs | 207 ++++++------------------------------------------- src/size.rs | 125 ++++++++--------------------- src/traits.rs | 22 ++++-- tests/main.rs | 78 ++++++++++--------- tests/serde.rs | 32 +++++--- 7 files changed, 134 insertions(+), 336 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b5d42f0182..95c11472cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "text-size" -version = "0.99.0-dev.1" +version = "0.99.0-dev.2" edition = "2018" authors = [ @@ -14,7 +14,6 @@ documentation = "https://docs.rs/text_unit" [dependencies] serde = { version = "1.0", optional = true, default_features = false } -deepsize = { version = "0.1", optional = true, default_features = false } [dev-dependencies] serde_test = "1.0" diff --git a/src/lib.rs b/src/lib.rs index 66bc653790..dc1a09b22b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,3 @@ mod traits; mod serde_impls; pub use crate::{range::TextRange, size::TextSize, traits::TextSized}; - -#[cfg(feature = "deepsize")] -deepsize::known_deep_size!(0, TextSize, TextRange); diff --git a/src/range.rs b/src/range.rs index 89012f2779..fe227cdaff 100644 --- a/src/range.rs +++ b/src/range.rs @@ -1,14 +1,10 @@ use { - crate::{TextSize, TextSized}, + crate::TextSize, std::{ cmp, convert::{TryFrom, TryInto}, fmt, - num::TryFromIntError, - ops::{ - Add, AddAssign, Bound, Index, IndexMut, Range, RangeBounds, RangeInclusive, RangeTo, - RangeToInclusive, Sub, SubAssign, - }, + ops::{Bound, Index, IndexMut, Range, RangeBounds}, }, }; @@ -19,17 +15,17 @@ use { /// /// # Translation from `text_unit` /// -/// - `TextRange::from_to(from, to)` ⟹ `TextRange::from(from..to)` -/// - `TextRange::offset_len(offset, size)` ⟹ `TextRange::at(offset).with_len(size)` -/// - `range.start()` ⟹ `range.start()` -/// - `range.end()` ⟹ `range.end()` -/// - `range.len()` ⟹ `range.len()` -/// - `range.is_empty()` ⟹ `range.is_empty()` -/// - `a.is_subrange(b)` ⟹ `b.contains(a)` -/// - `a.intersection(b)` ⟹ `TextRange::intersection(a, b)` -/// - `a.extend_to(b)` ⟹ `TextRange::covering(a, b)` -/// - `range.contains(offset)` ⟹ `range.contains_point(point)` -/// - `range.contains_inclusive(offset)` ⟹ `range.contains_point_inclusive(point)` +/// - `TextRange::from_to(from, to)` ⟹ `TextRange::from(from..to)` +/// - `TextRange::offset_len(offset, size)` ⟹ `TextRange::from(offset..offset + size)` +/// - `range.start()` ⟹ `range.start()` +/// - `range.end()` ⟹ `range.end()` +/// - `range.len()` ⟹ `range.len()` +/// - `range.is_empty()` ⟹ `range.is_empty()` +/// - `a.is_subrange(b)` ⟹ `b.contains(a)` +/// - `a.intersection(b)` ⟹ `TextRange::intersection(a, b)` +/// - `a.extend_to(b)` ⟹ `TextRange::covering(a, b)` +/// - `range.contains(offset)` ⟹ `range.contains_exclusive(point)` +/// - `range.contains_inclusive(offset)` ⟹ `range.contains_inclusive(point)` /// /// † See the note on [`TextRange::len`] for differing behavior for incorrect reverse ranges. #[derive(Copy, Clone, Eq, PartialEq, Hash)] @@ -44,12 +40,6 @@ pub(crate) const fn TextRange(start: TextSize, end: TextSize) -> TextRange { } impl fmt::Debug for TextRange { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self, f) - } -} - -impl fmt::Display for TextRange { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "[{}..{})", self.start(), self.end()) } @@ -91,27 +81,6 @@ impl TextRange { /// Manipulation methods. impl TextRange { - /// A range covering the text size of some text-like object. - pub fn of(size: impl TextSized) -> TextRange { - TextRange(0.into(), size.text_size()) - } - - /// An empty range at some text size offset. - pub fn at(size: impl Into) -> TextRange { - let size = size.into(); - TextRange(size, size) - } - - /// Set the length without changing the starting offset. - pub fn with_len(self, len: impl Into) -> TextRange { - TextRange(self.start(), self.start() + len.into()) - } - - /// Set the starting offset without changing the length. - pub fn with_offset(self, offset: impl Into) -> TextRange { - TextRange::at(offset).with_len(self.len()) - } - /// Check if this range completely contains another range. pub fn contains(self, other: TextRange) -> bool { self.start() <= other.start() && other.end() <= self.end() @@ -135,7 +104,7 @@ impl TextRange { /// Check if this range contains a point. /// /// The end index is considered excluded. - pub fn contains_point(self, point: impl Into) -> bool { + pub fn contains_exclusive(self, point: impl Into) -> bool { let point = point.into(); self.start() <= point && point < self.end() } @@ -143,40 +112,27 @@ impl TextRange { /// Check if this range contains a point. /// /// The end index is considered included. - pub fn contains_point_inclusive(self, point: impl Into) -> bool { + pub fn contains_inclusive(self, point: impl Into) -> bool { let point = point.into(); self.start() <= point && point <= self.end() } +} - /// Offset the entire range by some text size. - pub fn checked_add(self, rhs: impl TryInto) -> Option { - let rhs = rhs.try_into().ok()?; - Some(TextRange( - self.start().checked_add(rhs)?, - self.end().checked_add(rhs)?, - )) - } - - /// Offset the entire range by some text size. - pub fn checked_sub(self, rhs: impl TryInto) -> Option { - let rhs = rhs.try_into().ok()?; - Some(TextRange( - self.start().checked_sub(rhs)?, - self.end().checked_sub(rhs)?, - )) - } +fn ix(size: TextSize) -> usize { + size.try_into() + .unwrap_or_else(|_| panic!("overflow when converting TextSize to usize index")) } impl Index for str { type Output = str; fn index(&self, index: TextRange) -> &Self::Output { - &self[index.start().ix()..index.end().ix()] + &self[ix(index.start())..ix(index.end())] } } impl IndexMut for str { fn index_mut(&mut self, index: TextRange) -> &mut Self::Output { - &mut self[index.start().ix()..index.end().ix()] + &mut self[ix(index.start())..ix(index.end())] } } @@ -197,29 +153,7 @@ macro_rules! conversions { TextRange(value.start.into(), value.end.into()) } } - impl TryFrom> for TextRange { - type Error = TryFromIntError; - fn try_from(value: RangeInclusive<$lte>) -> Result { - let (start, end) = value.into_inner(); - let end: TextSize = end.into(); - // This is the only way to get a TryFromIntError currently. - let end = end.checked_add(1).ok_or_else(|| u8::try_from(-1).unwrap_err())?; - Ok(TextRange(start.into(), end)) - } - } - impl From> for TextRange { - fn from(value: RangeTo<$lte>) -> TextRange { - TextRange(0.into(), value.end.into()) - } - } - impl TryFrom> for TextRange { - type Error = TryFromIntError; - fn try_from(value: RangeToInclusive<$lte>) -> Result { - let start: TextSize = 0.into(); - let end: TextSize = value.end.into(); - TextRange::try_from(start..=end) - } - } + // Just support `start..end` for now, not `..end`, `start..=end`, `..=end`. }; (TryFrom<$gt:ident> for TextRange) => { impl TryFrom> for TextRange { @@ -228,30 +162,7 @@ macro_rules! conversions { Ok(TextRange(value.start.try_into()?, value.end.try_into()?)) } } - impl TryFrom> for TextRange { - type Error = TryFromIntError; - fn try_from(value: RangeInclusive<$gt>) -> Result { - let (start, end) = value.into_inner(); - let end: TextSize = end.try_into()?; - // This is the only way to get a TryFromIntError currently. - let end = end.checked_add(1).ok_or_else(|| u8::try_from(-1).unwrap_err())?; - Ok(TextRange(start.try_into()?, end)) - } - } - impl TryFrom> for TextRange { - type Error = TryFromIntError; - fn try_from(value: RangeTo<$gt>) -> Result { - Ok(TextRange(0.into(), value.end.try_into()?)) - } - } - impl TryFrom> for TextRange { - type Error = TryFromIntError; - fn try_from(value: RangeToInclusive<$gt>) -> Result { - let start: TextSize = 0.into(); - let end: TextSize = value.end.try_into()?; - TextRange::try_from(start..=end) - } - } + // Just support `start..end` for now, not `..end`, `start..=end`, `..=end`. }; { lt TextSize [$($lt:ident)*] @@ -260,11 +171,8 @@ macro_rules! conversions { varries [$($var:ident)*] } => { $( - // Not `From` yet because of integer type fallback. We want e.g. - // `TextRange::from(0)` and `range + 1` to work, and more `From` - // impls means that this will try (and fail) to use i32 rather - // than one of the unsigned integer types that actually work. - conversions!(TryFrom<$lt> for TextRange); + conversions!(From<$lt> for TextRange); + // unlike TextSize, we do not provide conversions in the "out" direction. )* $( @@ -288,68 +196,3 @@ conversions! { gt TextSize [u64] varries [usize] } - -impl Into for &'_ TextRange { - fn into(self) -> TextRange { - *self - } -} - -impl Into for &'_ mut TextRange { - fn into(self) -> TextRange { - *self - } -} - -macro_rules! op { - (impl $Op:ident for TextRange by fn $f:ident = $op:tt) => { - impl $Op for TextRange - where - TextSize: $Op, - { - type Output = TextRange; - fn $f(self, rhs: IntoSize) -> TextRange { - TextRange(self.start() $op rhs, self.end() $op rhs) - } - } - impl $Op for &'_ TextRange - where - TextRange: $Op, - { - type Output = TextRange; - fn $f(self, rhs: IntoSize) -> TextRange { - *self $op rhs - } - } - impl $Op for &'_ mut TextRange - where - TextRange: $Op, - { - type Output = TextRange; - fn $f(self, rhs: IntoSize) -> TextRange { - *self $op rhs - } - } - }; -} - -op!(impl Add for TextRange by fn add = +); -op!(impl Sub for TextRange by fn sub = -); - -impl AddAssign for TextRange -where - TextRange: Add, -{ - fn add_assign(&mut self, rhs: A) { - *self = *self + rhs - } -} - -impl SubAssign for TextRange -where - TextRange: Sub, -{ - fn sub_assign(&mut self, rhs: S) { - *self = *self - rhs - } -} diff --git a/src/size.rs b/src/size.rs index 80dba0aba6..43bf19dac9 100644 --- a/src/size.rs +++ b/src/size.rs @@ -16,10 +16,10 @@ use { /// /// # Translation from `text_unit` /// -/// - `TextUnit::of_char(c)` ⟹ `TextSize::of(c)` -/// - `TextUnit::of_str(s)` ⟹ `TextSize:of(s)` -/// - `TextUnit::from_usize(size)` ⟹ `TextSize::new(size)` -/// - `unit.to_usize()` ⟹ `size.ix()` +/// - `TextUnit::of_char(c)` ⟹ `TextSize::of(c)` +/// - `TextUnit::of_str(s)` ⟹ `TextSize:of(s)` +/// - `TextUnit::from_usize(size)` ⟹ `TextSize::try_from(size).unwrap_or_else(|| panic!(_))` +/// - `unit.to_usize()` ⟹ `usize::try_from(size).unwrap_or_else(|| panic!(_))` #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct TextSize { pub(crate) raw: u32, @@ -44,38 +44,16 @@ impl fmt::Display for TextSize { impl TextSize { /// The text size of some text-like object. - pub fn of(text: &impl TextSized) -> TextSize { + pub fn of(text: impl TextSized) -> TextSize { text.text_size() } - /// A text size for some `usize`. + /// A size of zero. /// - /// # Panics - /// - /// Panics if the size is greater than `u32::MAX` and debug assertions are - /// enabled. If debug assertions are not enabled, wraps into `u32` space. - pub fn new(size: usize) -> TextSize { - if let Ok(size) = size.try_into() { - size - } else if cfg!(debug_assertions) { - panic!("overflow when converting to TextSize"); - } else { - TextSize(size as u32) - } - } - - /// Convert this text size into the standard indexing type. - /// - /// # Panics - /// - /// Panics if the size is greater than `usize::MAX`. This can only - /// occur on targets where `size_of::() < size_of::()`. - pub fn ix(self) -> usize { - if let Ok(ix) = self.try_into() { - ix - } else { - panic!("overflow when converting TextSize to usize index") - } + /// This is equivalent to `TextSize::default()` or [`TextSize::MIN`], + /// but is more explicit on intent. + pub const fn zero() -> TextSize { + TextSize(0) } } @@ -88,14 +66,12 @@ impl TextSize { pub const MAX: TextSize = TextSize(u32::MAX); #[allow(missing_docs)] - pub fn checked_add(self, rhs: impl TryInto) -> Option { - let rhs = rhs.try_into().ok()?; + pub fn checked_add(self, rhs: TextSize) -> Option { self.raw.checked_add(rhs.raw).map(TextSize) } #[allow(missing_docs)] - pub fn checked_sub(self, rhs: impl TryInto) -> Option { - let rhs = rhs.try_into().ok()?; + pub fn checked_sub(self, rhs: TextSize) -> Option { self.raw.checked_sub(rhs.raw).map(TextSize) } } @@ -138,11 +114,7 @@ macro_rules! conversions { varries [$($var:ident)*] } => { $( - // Not `From` yet because of integer type fallback. We want e.g. - // `TextSize::from(0)` and `size + 1` to work, and more `From` - // impls means that this will try (and fail) to use i32 rather - // than one of the unsigned integer types that actually work. - conversions!(TryFrom<$lt> for TextSize); + conversions!(From<$lt> for TextSize); conversions!(TryFrom for $lt); )* @@ -167,54 +139,17 @@ conversions! { lt u32 [u8 u16] eq u32 [u32] gt u32 [u64] - varries [usize i32] // i32 so that `checked_add($lit)` (`try_from($lit)`) can work - // this will unfortunately have to hang around even if integer literal type fallback improves + varries [usize] } -impl Into for &'_ TextSize { - fn into(self) -> TextSize { - *self +// NB: We do not provide the transparent-ref impls like the stdlib does. +impl Add for TextSize { + type Output = TextSize; + fn add(self, rhs: TextSize) -> TextSize { + TextSize(self.raw + rhs.raw) } } -impl Into for &'_ mut TextSize { - fn into(self) -> TextSize { - *self - } -} - -macro_rules! op { - (impl $Op:ident for TextSize by fn $f:ident = $op:tt) => { - impl> $Op for TextSize { - type Output = TextSize; - fn $f(self, rhs: IntoSize) -> TextSize { - TextSize(self.raw $op rhs.into().raw) - } - } - impl $Op for &'_ TextSize - where - TextSize: $Op, - { - type Output = TextSize; - fn $f(self, rhs: IntoSize) -> TextSize { - *self $op rhs - } - } - impl $Op for &'_ mut TextSize - where - TextSize: $Op, - { - type Output = TextSize; - fn $f(self, rhs: IntoSize) -> TextSize { - *self $op rhs - } - } - }; -} - -op!(impl Add for TextSize by fn add = +); -op!(impl Sub for TextSize by fn sub = -); - impl AddAssign for TextSize where TextSize: Add, @@ -224,6 +159,13 @@ where } } +impl Sub for TextSize { + type Output = TextSize; + fn sub(self, rhs: TextSize) -> TextSize { + TextSize(self.raw - rhs.raw) + } +} + impl SubAssign for TextSize where TextSize: Sub, @@ -233,14 +175,11 @@ where } } -impl iter::Sum for TextSize { - fn sum>(iter: I) -> TextSize { - iter.fold(TextSize::default(), Add::add) - } -} - -impl<'a> iter::Sum<&'a Self> for TextSize { - fn sum>(iter: I) -> Self { - iter.fold(TextSize::default(), Add::add) +impl iter::Sum for TextSize +where + TextSize: Add, +{ + fn sum>(iter: I) -> TextSize { + iter.fold(TextSize::zero(), Add::add) } } diff --git a/src/traits.rs b/src/traits.rs index 52601534d2..877f057895 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -4,26 +4,32 @@ use { }; /// Text-like structures that have a text size. -pub trait TextSized { +pub trait TextSized: Copy { /// The size of this text-alike. - fn text_size(&self) -> TextSize; + fn text_size(self) -> TextSize; } -impl TextSized for str { - fn text_size(&self) -> TextSize { +impl TextSized for &'_ str { + fn text_size(self) -> TextSize { let len = self.len(); - TextSize::new(len) + if let Ok(size) = len.try_into() { + size + } else if cfg!(debug_assertions) { + panic!("overflow when converting to TextSize"); + } else { + TextSize(len as u32) + } } } impl TextSized for char { - fn text_size(&self) -> TextSize { - self.len_utf8().try_into().unwrap() + fn text_size(self) -> TextSize { + TextSize(self.len_utf8() as u32) } } impl TextSized for TextRange { - fn text_size(&self) -> TextSize { + fn text_size(self) -> TextSize { self.len() } } diff --git a/tests/main.rs b/tests/main.rs index a7eef0a2cd..3288c27320 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -1,67 +1,73 @@ -use text_size::*; +use {std::ops, text_size::*}; -fn r(from: u32, to: u32) -> TextRange { - TextRange::from(from..to) +fn size(x: u32) -> TextSize { + TextSize::from(x) +} + +fn range(x: ops::Range) -> TextRange { + TextRange::from(x) } #[test] fn sum() { - let xs: Vec = vec![0.into(), 1.into(), 2.into()]; - assert_eq!(xs.iter().sum::(), 3.into()); - assert_eq!(xs.into_iter().sum::(), 3.into()); + let xs: Vec = vec![size(0), size(1), size(2)]; + assert_eq!(xs.iter().copied().sum::(), size(3)); + assert_eq!(xs.into_iter().sum::(), size(3)); } #[test] fn math() { - let range = r(10, 20); - assert_eq!(range + 5, r(15, 25)); - assert_eq!(range - 5, r(5, 15)); + assert_eq!(size(10) + size(5), size(15)); + assert_eq!(size(10) - size(5), size(5)); } #[test] fn checked_math() { - let x: TextSize = 1.into(); - assert_eq!(x.checked_sub(1), Some(0.into())); - assert_eq!(x.checked_sub(2), None); - - assert_eq!(r(1, 2).checked_sub(1), Some(r(0, 1))); - assert_eq!(x.checked_sub(2), None); + assert_eq!(size(1).checked_add(size(1)), Some(size(2))); + assert_eq!(size(1).checked_sub(size(1)), Some(size(0))); + assert_eq!(size(1).checked_sub(size(2)), None); + assert_eq!(TextSize::MAX.checked_add(size(1)), None); } #[test] +#[rustfmt::skip] fn contains() { - let r1 = r(2, 4); - let r2 = r(2, 3); - let r3 = r(1, 3); - assert!(r1.contains(r2)); - assert!(!r1.contains(r3)); + assert!( range(2..4).contains(range(2..3))); + assert!( ! range(2..4).contains(range(1..3))); } #[test] fn intersection() { - assert_eq!(TextRange::intersection(r(1, 2), r(2, 3)), Some(r(2, 2))); - assert_eq!(TextRange::intersection(r(1, 5), r(2, 3)), Some(r(2, 3))); - assert_eq!(TextRange::intersection(r(1, 2), r(3, 4)), None); + assert_eq!( + TextRange::intersection(range(1..2), range(2..3)), + Some(range(2..2)) + ); + assert_eq!( + TextRange::intersection(range(1..5), range(2..3)), + Some(range(2..3)) + ); + assert_eq!(TextRange::intersection(range(1..2), range(3..4)), None); } #[test] fn covering() { - assert_eq!(TextRange::covering(r(1, 2), r(2, 3)), r(1, 3)); - assert_eq!(TextRange::covering(r(1, 5), r(2, 3)), r(1, 5)); - assert_eq!(TextRange::covering(r(1, 2), r(4, 5)), r(1, 5)); + assert_eq!(TextRange::covering(range(1..2), range(2..3)), range(1..3)); + assert_eq!(TextRange::covering(range(1..5), range(2..3)), range(1..5)); + assert_eq!(TextRange::covering(range(1..2), range(4..5)), range(1..5)); } #[test] +#[rustfmt::skip] fn contains_point() { - assert!(!r(1, 3).contains_point(0)); - assert!(r(1, 3).contains_point(1)); - assert!(r(1, 3).contains_point(2)); - assert!(!r(1, 3).contains_point(3)); - assert!(!r(1, 3).contains_point(4)); + assert!( ! range(1..3).contains_exclusive(size(0))); + assert!( range(1..3).contains_exclusive(size(1))); + assert!( range(1..3).contains_exclusive(size(2))); + assert!( ! range(1..3).contains_exclusive(size(3))); + assert!( ! range(1..3).contains_exclusive(size(4))); - assert!(!r(1, 3).contains_point_inclusive(0)); - assert!(r(1, 3).contains_point_inclusive(1)); - assert!(r(1, 3).contains_point_inclusive(2)); - assert!(r(1, 3).contains_point_inclusive(3)); - assert!(!r(1, 3).contains_point_inclusive(4)); + assert!( ! range(1..3).contains_inclusive(size(0))); + assert!( range(1..3).contains_inclusive(size(1))); + assert!( range(1..3).contains_inclusive(size(2))); + assert!( range(1..3).contains_inclusive(size(3))); + assert!( ! range(1..3).contains_inclusive(size(4))); } diff --git a/tests/serde.rs b/tests/serde.rs index 439b9d71f5..62254634dd 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -1,17 +1,25 @@ -use {serde_test::*, text_size::*}; +use {serde_test::*, std::ops, text_size::*}; -#[test] -fn size() { - assert_tokens(&TextSize::new(00), &[Token::U32(00)]); - assert_tokens(&TextSize::new(10), &[Token::U32(10)]); - assert_tokens(&TextSize::new(20), &[Token::U32(20)]); - assert_tokens(&TextSize::new(30), &[Token::U32(30)]); +fn size(x: u32) -> TextSize { + TextSize::from(x) +} + +fn range(x: ops::Range) -> TextRange { + TextRange::from(x) } #[test] -fn range() { +fn size_serialization() { + assert_tokens(&size(00), &[Token::U32(00)]); + assert_tokens(&size(10), &[Token::U32(10)]); + assert_tokens(&size(20), &[Token::U32(20)]); + assert_tokens(&size(30), &[Token::U32(30)]); +} + +#[test] +fn range_serialization() { assert_tokens( - &TextRange::from(00..10), + &range(00..10), &[ Token::Tuple { len: 2 }, Token::U32(00), @@ -20,7 +28,7 @@ fn range() { ], ); assert_tokens( - &TextRange::from(10..20), + &range(10..20), &[ Token::Tuple { len: 2 }, Token::U32(10), @@ -29,7 +37,7 @@ fn range() { ], ); assert_tokens( - &TextRange::from(20..30), + &range(20..30), &[ Token::Tuple { len: 2 }, Token::U32(20), @@ -38,7 +46,7 @@ fn range() { ], ); assert_tokens( - &TextRange::from(30..40), + &range(30..40), &[ Token::Tuple { len: 2 }, Token::U32(30), From cd1f821c61d9488827482227f2a1ea73dbed5e6e Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 9 Mar 2020 08:37:26 +0100 Subject: [PATCH 18/87] Enforce invariant that ranges are well-formed This is in contrast to `std::ops::Range`, which are not guaranteed to be proper. For this reason, we have to lose `From` impls as well. --- src/range.rs | 88 ++++++++++++--------------------------------------- tests/main.rs | 2 +- 2 files changed, 21 insertions(+), 69 deletions(-) diff --git a/src/range.rs b/src/range.rs index fe227cdaff..8febcad082 100644 --- a/src/range.rs +++ b/src/range.rs @@ -2,9 +2,9 @@ use { crate::TextSize, std::{ cmp, - convert::{TryFrom, TryInto}, + convert::TryInto, fmt, - ops::{Bound, Index, IndexMut, Range, RangeBounds}, + ops::{Bound, Index, IndexMut, RangeBounds}, }, }; @@ -30,15 +30,11 @@ use { /// † See the note on [`TextRange::len`] for differing behavior for incorrect reverse ranges. #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct TextRange { + // Invariant: start <= end start: TextSize, end: TextSize, } -#[allow(non_snake_case)] -pub(crate) const fn TextRange(start: TextSize, end: TextSize) -> TextRange { - TextRange { start, end } -} - impl fmt::Debug for TextRange { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "[{}..{})", self.start(), self.end()) @@ -47,6 +43,16 @@ impl fmt::Debug for TextRange { /// Identity methods. impl TextRange { + /// Creates a new `TextRange` with given `start` and `end. + /// + /// # Panics + /// + /// Panics if `end < start`. + pub fn new(start: TextSize, end: TextSize) -> TextRange { + assert!(start <= end); + TextRange { start, end } + } + /// The start point of this range. pub const fn start(self) -> TextSize { self.start @@ -58,11 +64,6 @@ impl TextRange { } /// The size of this range. - /// - /// # Panics - /// - /// When `end() < start()`, triggers a subtraction overflow. - /// This will panic with debug assertions, and overflow without. pub const fn len(self) -> TextSize { // HACK for const fn: math on primitives only TextSize(self.end().raw - self.start().raw) @@ -71,11 +72,10 @@ impl TextRange { /// Check if this range empty or reversed. /// /// When `end() < start()`, this returns false. - /// Code should prefer `is_empty()` to `len() == 0`, - /// as this safeguards against incorrect reverse ranges. + /// Code should prefer `is_empty()` to `len() == 0`. pub const fn is_empty(self) -> bool { // HACK for const fn: math on primitives only - self.start().raw >= self.end().raw + self.start().raw == self.end().raw } } @@ -91,14 +91,17 @@ impl TextRange { pub fn intersection(lhs: TextRange, rhs: TextRange) -> Option { let start = cmp::max(lhs.start(), rhs.start()); let end = cmp::min(lhs.end(), rhs.end()); - Some(TextRange(start, end)).filter(|_| start <= end) + if end < start { + return None; + } + Some(TextRange::new(start, end)) } /// The smallest range that completely contains both ranges. pub fn covering(lhs: TextRange, rhs: TextRange) -> TextRange { let start = cmp::min(lhs.start(), rhs.start()); let end = cmp::max(lhs.end(), rhs.end()); - TextRange(start, end) + TextRange::new(start, end) } /// Check if this range contains a point. @@ -145,54 +148,3 @@ impl RangeBounds for TextRange { Bound::Excluded(&self.end) } } - -macro_rules! conversions { - (From<$lte:ident> for TextRange) => { - impl From> for TextRange { - fn from(value: Range<$lte>) -> TextRange { - TextRange(value.start.into(), value.end.into()) - } - } - // Just support `start..end` for now, not `..end`, `start..=end`, `..=end`. - }; - (TryFrom<$gt:ident> for TextRange) => { - impl TryFrom> for TextRange { - type Error = <$gt as TryInto>::Error; - fn try_from(value: Range<$gt>) -> Result { - Ok(TextRange(value.start.try_into()?, value.end.try_into()?)) - } - } - // Just support `start..end` for now, not `..end`, `start..=end`, `..=end`. - }; - { - lt TextSize [$($lt:ident)*] - eq TextSize [$($eq:ident)*] - gt TextSize [$($gt:ident)*] - varries [$($var:ident)*] - } => { - $( - conversions!(From<$lt> for TextRange); - // unlike TextSize, we do not provide conversions in the "out" direction. - )* - - $( - conversions!(From<$eq> for TextRange); - )* - - $( - conversions!(TryFrom<$gt> for TextRange); - )* - - $( - conversions!(TryFrom<$var> for TextRange); - )* - }; -} - -// FIXME: when `default impl` is usable, change to blanket impls for [Try]Into instead -conversions! { - lt TextSize [u8 u16] - eq TextSize [u32 TextSize] - gt TextSize [u64] - varries [usize] -} diff --git a/tests/main.rs b/tests/main.rs index 3288c27320..e25f59bacf 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -5,7 +5,7 @@ fn size(x: u32) -> TextSize { } fn range(x: ops::Range) -> TextRange { - TextRange::from(x) + TextRange::new(x.start.into(), x.end.into()) } #[test] From da935943cc9d5198bf42d5d6b705fbf8672c17ae Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 9 Mar 2020 16:27:52 +0100 Subject: [PATCH 19/87] Tuple-struct ctor is the canonical way to create a ranges --- src/range.rs | 25 +++++++++++++------------ tests/main.rs | 2 +- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/range.rs b/src/range.rs index 8febcad082..7e01b12e9f 100644 --- a/src/range.rs +++ b/src/range.rs @@ -41,18 +41,19 @@ impl fmt::Debug for TextRange { } } +/// Creates a new `TextRange` with given `start` and `end. +/// +/// # Panics +/// +/// Panics if `end < start`. +#[allow(non_snake_case)] +pub fn TextRange(start: TextSize, end: TextSize) -> TextRange { + assert!(start <= end); + TextRange { start, end } +} + /// Identity methods. impl TextRange { - /// Creates a new `TextRange` with given `start` and `end. - /// - /// # Panics - /// - /// Panics if `end < start`. - pub fn new(start: TextSize, end: TextSize) -> TextRange { - assert!(start <= end); - TextRange { start, end } - } - /// The start point of this range. pub const fn start(self) -> TextSize { self.start @@ -94,14 +95,14 @@ impl TextRange { if end < start { return None; } - Some(TextRange::new(start, end)) + Some(TextRange(start, end)) } /// The smallest range that completely contains both ranges. pub fn covering(lhs: TextRange, rhs: TextRange) -> TextRange { let start = cmp::min(lhs.start(), rhs.start()); let end = cmp::max(lhs.end(), rhs.end()); - TextRange::new(start, end) + TextRange(start, end) } /// Check if this range contains a point. diff --git a/tests/main.rs b/tests/main.rs index e25f59bacf..9a20cf9819 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -5,7 +5,7 @@ fn size(x: u32) -> TextSize { } fn range(x: ops::Range) -> TextRange { - TextRange::new(x.start.into(), x.end.into()) + TextRange(x.start.into(), x.end.into()) } #[test] From 578ec6f11dc0911bf802b978b23253ffec01e90f Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 9 Mar 2020 16:56:35 +0100 Subject: [PATCH 20/87] Remove stray Intos --- src/range.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/range.rs b/src/range.rs index 7e01b12e9f..16e52e1e4e 100644 --- a/src/range.rs +++ b/src/range.rs @@ -105,19 +105,18 @@ impl TextRange { TextRange(start, end) } - /// Check if this range contains a point. + /// Check if this range contains an offset. /// /// The end index is considered excluded. - pub fn contains_exclusive(self, point: impl Into) -> bool { - let point = point.into(); - self.start() <= point && point < self.end() + pub fn contains_exclusive(self, offset: TextSize) -> bool { + self.start() <= offset && offset < self.end() } - /// Check if this range contains a point. + /// Check if this range contains an offset. /// /// The end index is considered included. - pub fn contains_inclusive(self, point: impl Into) -> bool { - let point = point.into(); + pub fn contains_inclusive(self, offset: TextSize) -> bool { + let point = offset.into(); self.start() <= point && point <= self.end() } } From c3e4019ad4c8a98a71bb4ed2cf2a3938ec94614d Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 9 Mar 2020 08:22:41 +0100 Subject: [PATCH 21/87] Drop unnecessary impl The impl is valid, but probably not too useful, we can always add it later --- src/traits.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/traits.rs b/src/traits.rs index 877f057895..d90f3d431c 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,7 +1,4 @@ -use { - crate::{TextRange, TextSize}, - std::convert::TryInto, -}; +use {crate::TextSize, std::convert::TryInto}; /// Text-like structures that have a text size. pub trait TextSized: Copy { @@ -27,9 +24,3 @@ impl TextSized for char { TextSize(self.len_utf8() as u32) } } - -impl TextSized for TextRange { - fn text_size(self) -> TextSize { - self.len() - } -} From 3920cac1192825f356303813dde83723b6a3ff0e Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 9 Mar 2020 08:44:47 +0100 Subject: [PATCH 22/87] Document MSRV --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index dc1a09b22b..32262de12c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,6 @@ //! Newtypes for working with text sizes/ranges in a more type-safe manner. +//! +//! Minimal Supported Rust Version: latest stable. #![forbid(unsafe_code)] #![warn(missing_debug_implementations, missing_docs)] From 260aa8a118e356e8cf84a3f09957b5909e825ddd Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 9 Mar 2020 16:47:50 +0100 Subject: [PATCH 23/87] Add TextRange::empty --- src/range.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/range.rs b/src/range.rs index 7e01b12e9f..f30b90141e 100644 --- a/src/range.rs +++ b/src/range.rs @@ -54,6 +54,14 @@ pub fn TextRange(start: TextSize, end: TextSize) -> TextRange { /// Identity methods. impl TextRange { + /// Creates a zero-length range at the specified offset. + pub const fn empty(self, offset: TextSize) -> TextRange { + TextRange { + start: offset, + end: offset, + } + } + /// The start point of this range. pub const fn start(self) -> TextSize { self.start From 0f4acdbdbe2fcdec8123a4a6c01707fecad98ef4 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 10 Mar 2020 13:56:50 +0100 Subject: [PATCH 24/87] Alternative set of conversions --- src/size.rs | 85 ++++++++++++++--------------------------------------- 1 file changed, 22 insertions(+), 63 deletions(-) diff --git a/src/size.rs b/src/size.rs index 43bf19dac9..47de00c4e3 100644 --- a/src/size.rs +++ b/src/size.rs @@ -1,7 +1,7 @@ use { crate::TextSized, std::{ - convert::{TryFrom, TryInto}, + convert::TryFrom, fmt, iter, num::TryFromIntError, ops::{Add, AddAssign, Sub, SubAssign}, @@ -76,70 +76,29 @@ impl TextSize { } } -macro_rules! conversions { - (From for $gte:ident) => { - impl From for $gte { - fn from(value: TextSize) -> $gte { - value.raw.into() - } - } - }; - (From<$lte:ident> for TextSize) => { - impl From<$lte> for TextSize { - fn from(value: $lte) -> TextSize { - TextSize(value.into()) - } - } - }; - (TryFrom for $lt:ident) => { - impl TryFrom for $lt { - type Error = TryFromIntError; - fn try_from(value: TextSize) -> Result<$lt, Self::Error> { - value.raw.try_into() - } - } - }; - (TryFrom<$gt:ident> for TextSize) => { - impl TryFrom<$gt> for TextSize { - type Error = <$gt as TryInto>::Error; - fn try_from(value: $gt) -> Result { - value.try_into().map(TextSize) - } - } - }; - { - lt u32 [$($lt:ident)*] - eq u32 [$($eq:ident)*] - gt u32 [$($gt:ident)*] - varries [$($var:ident)*] - } => { - $( - conversions!(From<$lt> for TextSize); - conversions!(TryFrom for $lt); - )* - - $( - conversions!(From<$eq> for TextSize); - conversions!(From for $eq); - )* - - $( - conversions!(TryFrom<$gt> for TextSize); - conversions!(From for $gt); - )* - - $( - conversions!(TryFrom<$var> for TextSize); - conversions!(TryFrom for $var); - )* - }; +impl From for TextSize { + fn from(raw: u32) -> Self { + TextSize { raw } + } } -conversions! { - lt u32 [u8 u16] - eq u32 [u32] - gt u32 [u64] - varries [usize] +impl From for u32 { + fn from(value: TextSize) -> Self { + value.raw + } +} + +impl TryFrom for TextSize { + type Error = TryFromIntError; + fn try_from(value: usize) -> Result { + Ok(u32::try_from(value)?.into()) + } +} + +impl From for usize { + fn from(value: TextSize) -> Self { + value.raw as usize + } } // NB: We do not provide the transparent-ref impls like the stdlib does. From 83719155e8bea3982427b930f775944fa0910294 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 10 Mar 2020 14:12:47 +0100 Subject: [PATCH 25/87] Provide the same set of impls as stdlib --- src/size.rs | 68 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/src/size.rs b/src/size.rs index 43bf19dac9..a51a279e9e 100644 --- a/src/size.rs +++ b/src/size.rs @@ -142,38 +142,46 @@ conversions! { varries [usize] } -// NB: We do not provide the transparent-ref impls like the stdlib does. -impl Add for TextSize { - type Output = TextSize; - fn add(self, rhs: TextSize) -> TextSize { - TextSize(self.raw + rhs.raw) - } +macro_rules! arith { + ($Op:ident $op:ident, $OpAssign:ident $op_assign:ident) => { + impl $Op for TextSize { + type Output = TextSize; + fn $op(self, rhs: TextSize) -> TextSize { + TextSize($Op::$op(self.raw, rhs.raw)) + } + } + impl $Op for &'_ TextSize { + type Output = TextSize; + fn $op(self, rhs: TextSize) -> TextSize { + TextSize($Op::$op(self.raw, rhs.raw)) + } + } + impl $Op<&'_ TextSize> for TextSize { + type Output = TextSize; + fn $op(self, rhs: &TextSize) -> TextSize { + TextSize($Op::$op(self.raw, rhs.raw)) + } + } + impl $Op<&'_ TextSize> for &'_ TextSize { + type Output = TextSize; + fn $op(self, rhs: &TextSize) -> TextSize { + TextSize($Op::$op(self.raw, rhs.raw)) + } + } + + impl $OpAssign for TextSize + where + TextSize: $Op, + { + fn $op_assign(&mut self, rhs: A) { + *self = $Op::$op(*self, rhs) + } + } + }; } -impl AddAssign for TextSize -where - TextSize: Add, -{ - fn add_assign(&mut self, rhs: A) { - *self = *self + rhs - } -} - -impl Sub for TextSize { - type Output = TextSize; - fn sub(self, rhs: TextSize) -> TextSize { - TextSize(self.raw - rhs.raw) - } -} - -impl SubAssign for TextSize -where - TextSize: Sub, -{ - fn sub_assign(&mut self, rhs: S) { - *self = *self - rhs - } -} +arith!(Add add, AddAssign add_assign); +arith!(Sub sub, SubAssign sub_assign); impl iter::Sum for TextSize where From ac45c857fd414e28c3739ccdb2e086b4b9d74c83 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Sun, 8 Mar 2020 14:46:52 -0400 Subject: [PATCH 26/87] Remove Display for TextSize --- src/range.rs | 2 +- src/size.rs | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/range.rs b/src/range.rs index 7fb4987149..e70a7b6a33 100644 --- a/src/range.rs +++ b/src/range.rs @@ -37,7 +37,7 @@ pub struct TextRange { impl fmt::Debug for TextRange { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "[{}..{})", self.start(), self.end()) + write!(f, "[{}..{})", self.start().raw, self.end().raw) } } diff --git a/src/size.rs b/src/size.rs index 43bf19dac9..26434beabd 100644 --- a/src/size.rs +++ b/src/size.rs @@ -32,13 +32,7 @@ pub(crate) const fn TextSize(raw: u32) -> TextSize { impl fmt::Debug for TextSize { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self, f) - } -} - -impl fmt::Display for TextSize { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.raw, f) + write!(f, "{}", self.raw) } } From 64dbce039c45f7940d72245e9745a70a67f8f730 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Wed, 11 Mar 2020 23:27:12 -0400 Subject: [PATCH 27/87] fix tests --- tests/serde.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/serde.rs b/tests/serde.rs index 62254634dd..6be1c93815 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -5,7 +5,7 @@ fn size(x: u32) -> TextSize { } fn range(x: ops::Range) -> TextRange { - TextRange::from(x) + TextRange(x.start.into(), x.end.into()) } #[test] From b8621776f11d2c7ba43df9206fa6a0a109f8acc8 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 12 Mar 2020 12:31:37 +0100 Subject: [PATCH 28/87] Be 16-bit clean --- src/size.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/size.rs b/src/size.rs index 47de00c4e3..5e1c2edfd8 100644 --- a/src/size.rs +++ b/src/size.rs @@ -97,7 +97,12 @@ impl TryFrom for TextSize { impl From for usize { fn from(value: TextSize) -> Self { - value.raw as usize + assert_lossless_conversion(); + return value.raw as usize; + + const fn assert_lossless_conversion() { + [()][(std::mem::size_of::() < std::mem::size_of::()) as usize] + } } } From d0560c7002c994b9d224080cd7b25b65918edec3 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 12 Mar 2020 15:57:07 +0100 Subject: [PATCH 29/87] Fix ix --- src/range.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/range.rs b/src/range.rs index e70a7b6a33..c06c19dfe4 100644 --- a/src/range.rs +++ b/src/range.rs @@ -1,9 +1,7 @@ use { crate::TextSize, std::{ - cmp, - convert::TryInto, - fmt, + cmp, fmt, ops::{Bound, Index, IndexMut, RangeBounds}, }, }; @@ -130,8 +128,7 @@ impl TextRange { } fn ix(size: TextSize) -> usize { - size.try_into() - .unwrap_or_else(|_| panic!("overflow when converting TextSize to usize index")) + size.into() } impl Index for str { From 85b96cf817ddafde2ed121ae0a44deae4190b8d6 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 12 Mar 2020 16:16:53 +0100 Subject: [PATCH 30/87] Alternative set of contains function Motivation: TextRange is a set, it contains elements (TextSize). For this reason, for range-range op we use a more verbose `contains_range` name. In stdlib, there's `HashSet::is_subset`. We used a similar design with `is_subrage` before, but it was very confusing in practice -- you'll have to lookup docs for which of lhs and rhs is sub and super set. Additionally, exclusive semantics is a clear default with better properties (if you have a partitioning of a range into subranges, only one of the parts contains any given offset), so it make sense to call it `contains` and reserve `contains_inclusive` for another op. --- src/range.rs | 32 ++++++++++++++++---------------- tests/main.rs | 14 +++++++------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/range.rs b/src/range.rs index c06c19dfe4..4fba210cfb 100644 --- a/src/range.rs +++ b/src/range.rs @@ -88,8 +88,23 @@ impl TextRange { /// Manipulation methods. impl TextRange { + /// Check if this range contains an offset. + /// + /// The end index is considered excluded. + pub fn contains(self, offset: TextSize) -> bool { + self.start() <= offset && offset < self.end() + } + + /// Check if this range contains an offset. + /// + /// The end index is considered included. + pub fn contains_inclusive(self, offset: TextSize) -> bool { + let point = offset.into(); + self.start() <= point && point <= self.end() + } + /// Check if this range completely contains another range. - pub fn contains(self, other: TextRange) -> bool { + pub fn contains_range(self, other: TextRange) -> bool { self.start() <= other.start() && other.end() <= self.end() } @@ -110,21 +125,6 @@ impl TextRange { let end = cmp::max(lhs.end(), rhs.end()); TextRange(start, end) } - - /// Check if this range contains an offset. - /// - /// The end index is considered excluded. - pub fn contains_exclusive(self, offset: TextSize) -> bool { - self.start() <= offset && offset < self.end() - } - - /// Check if this range contains an offset. - /// - /// The end index is considered included. - pub fn contains_inclusive(self, offset: TextSize) -> bool { - let point = offset.into(); - self.start() <= point && point <= self.end() - } } fn ix(size: TextSize) -> usize { diff --git a/tests/main.rs b/tests/main.rs index 9a20cf9819..cd299d0b66 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -32,8 +32,8 @@ fn checked_math() { #[test] #[rustfmt::skip] fn contains() { - assert!( range(2..4).contains(range(2..3))); - assert!( ! range(2..4).contains(range(1..3))); + assert!( range(2..4).contains_range(range(2..3))); + assert!( ! range(2..4).contains_range(range(1..3))); } #[test] @@ -59,11 +59,11 @@ fn covering() { #[test] #[rustfmt::skip] fn contains_point() { - assert!( ! range(1..3).contains_exclusive(size(0))); - assert!( range(1..3).contains_exclusive(size(1))); - assert!( range(1..3).contains_exclusive(size(2))); - assert!( ! range(1..3).contains_exclusive(size(3))); - assert!( ! range(1..3).contains_exclusive(size(4))); + assert!( ! range(1..3).contains(size(0))); + assert!( range(1..3).contains(size(1))); + assert!( range(1..3).contains(size(2))); + assert!( ! range(1..3).contains(size(3))); + assert!( ! range(1..3).contains(size(4))); assert!( ! range(1..3).contains_inclusive(size(0))); assert!( range(1..3).contains_inclusive(size(1))); From a93dd2aa2eab267b7b5f66880c47222ef31e568b Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 13 Mar 2020 10:26:24 +0100 Subject: [PATCH 31/87] Add a `From` impl --- src/range.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/range.rs b/src/range.rs index c06c19dfe4..60a34450c7 100644 --- a/src/range.rs +++ b/src/range.rs @@ -2,7 +2,7 @@ use { crate::TextSize, std::{ cmp, fmt, - ops::{Bound, Index, IndexMut, RangeBounds}, + ops::{Bound, Index, IndexMut, Range, RangeBounds}, }, }; @@ -127,20 +127,16 @@ impl TextRange { } } -fn ix(size: TextSize) -> usize { - size.into() -} - impl Index for str { type Output = str; fn index(&self, index: TextRange) -> &Self::Output { - &self[ix(index.start())..ix(index.end())] + &self[Range::::from(index)] } } impl IndexMut for str { fn index_mut(&mut self, index: TextRange) -> &mut Self::Output { - &mut self[ix(index.start())..ix(index.end())] + &mut self[Range::::from(index)] } } @@ -153,3 +149,12 @@ impl RangeBounds for TextRange { Bound::Excluded(&self.end) } } + +impl From for Range +where + T: From, +{ + fn from(r: TextRange) -> Self { + r.start().into()..r.end().into() + } +} From e5bb71d61c9829350f413df7cd6b662fcf163ae5 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 12 Mar 2020 15:53:47 +0100 Subject: [PATCH 32/87] Switch to Rust range notation --- src/range.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/range.rs b/src/range.rs index e70a7b6a33..73af2896ff 100644 --- a/src/range.rs +++ b/src/range.rs @@ -37,7 +37,7 @@ pub struct TextRange { impl fmt::Debug for TextRange { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "[{}..{})", self.start().raw, self.end().raw) + write!(f, "{}..{}", self.start().raw, self.end().raw) } } From ed056c48a408f9b7967cb7668d9396010f53c03e Mon Sep 17 00:00:00 2001 From: CAD97 Date: Sun, 8 Mar 2020 14:34:47 -0400 Subject: [PATCH 33/87] Provide generic ref-removing ops for TextSize --- src/size.rs | 58 ++++++++++++++++++++++++++++----------------------- tests/main.rs | 2 +- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/size.rs b/src/size.rs index 867a0004ae..454327a015 100644 --- a/src/size.rs +++ b/src/size.rs @@ -100,46 +100,52 @@ impl From for usize { } } -macro_rules! arith { - ($Op:ident $op:ident, $OpAssign:ident $op_assign:ident) => { +macro_rules! ops { + (impl $Op:ident for TextSize by fn $f:ident = $op:tt) => { impl $Op for TextSize { type Output = TextSize; - fn $op(self, rhs: TextSize) -> TextSize { - TextSize($Op::$op(self.raw, rhs.raw)) + fn $f(self, other: TextSize) -> TextSize { + TextSize(self.raw $op other.raw) } } - impl $Op for &'_ TextSize { + impl $Op<&TextSize> for TextSize { type Output = TextSize; - fn $op(self, rhs: TextSize) -> TextSize { - TextSize($Op::$op(self.raw, rhs.raw)) + fn $f(self, other: &TextSize) -> TextSize { + self $op *other } } - impl $Op<&'_ TextSize> for TextSize { - type Output = TextSize; - fn $op(self, rhs: &TextSize) -> TextSize { - TextSize($Op::$op(self.raw, rhs.raw)) - } - } - impl $Op<&'_ TextSize> for &'_ TextSize { - type Output = TextSize; - fn $op(self, rhs: &TextSize) -> TextSize { - TextSize($Op::$op(self.raw, rhs.raw)) - } - } - - impl $OpAssign for TextSize + impl $Op for &TextSize where - TextSize: $Op, + TextSize: $Op, { - fn $op_assign(&mut self, rhs: A) { - *self = $Op::$op(*self, rhs) + type Output = TextSize; + fn $f(self, other: T) -> TextSize { + *self $op other } } }; } -arith!(Add add, AddAssign add_assign); -arith!(Sub sub, SubAssign sub_assign); +ops!(impl Add for TextSize by fn add = +); +ops!(impl Sub for TextSize by fn sub = -); + +impl AddAssign for TextSize +where + TextSize: Add, +{ + fn add_assign(&mut self, rhs: A) { + *self = *self + rhs + } +} + +impl SubAssign for TextSize +where + TextSize: Sub, +{ + fn sub_assign(&mut self, rhs: S) { + *self = *self - rhs + } +} impl iter::Sum for TextSize where diff --git a/tests/main.rs b/tests/main.rs index 9a20cf9819..66b6106671 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -11,7 +11,7 @@ fn range(x: ops::Range) -> TextRange { #[test] fn sum() { let xs: Vec = vec![size(0), size(1), size(2)]; - assert_eq!(xs.iter().copied().sum::(), size(3)); + assert_eq!(xs.iter().sum::(), size(3)); assert_eq!(xs.into_iter().sum::(), size(3)); } From e07fb7cc5f6e944069b5d4096c03d95df45b2a99 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 13 Mar 2020 21:28:14 +0100 Subject: [PATCH 34/87] Check invariant during deserialization --- src/serde_impls.rs | 11 +++++++++-- tests/serde.rs | 22 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/serde_impls.rs b/src/serde_impls.rs index 1963413fd8..617e99cc02 100644 --- a/src/serde_impls.rs +++ b/src/serde_impls.rs @@ -1,6 +1,6 @@ use { crate::{TextRange, TextSize}, - serde::{Deserialize, Deserializer, Serialize, Serializer}, + serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}, }; impl Serialize for TextSize { @@ -35,6 +35,13 @@ impl<'de> Deserialize<'de> for TextRange { where D: Deserializer<'de>, { - Deserialize::deserialize(deserializer).map(|(start, end)| TextRange(start, end)) + let (start, end) = Deserialize::deserialize(deserializer)?; + if !(start <= end) { + return Err(Error::custom(format!( + "invalid range: {:?}..{:?}", + start, end + ))); + } + Ok(TextRange(start, end)) } } diff --git a/tests/serde.rs b/tests/serde.rs index 6be1c93815..a32f89e865 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -55,3 +55,25 @@ fn range_serialization() { ], ); } + +#[test] +fn invalid_range_deserialization() { + assert_tokens::( + &range(62..92), + &[ + Token::Tuple { len: 2 }, + Token::U32(62), + Token::U32(92), + Token::TupleEnd, + ], + ); + assert_de_tokens_error::( + &[ + Token::Tuple { len: 2 }, + Token::U32(92), + Token::U32(62), + Token::TupleEnd, + ], + "invalid range: 92..62", + ); +} From d05705c4f6f1f3039de620899f22b6c384a3ceca Mon Sep 17 00:00:00 2001 From: CAD97 Date: Sat, 14 Mar 2020 21:50:15 -0400 Subject: [PATCH 35/87] minor serde style fix --- src/serde_impls.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/serde_impls.rs b/src/serde_impls.rs index 617e99cc02..6a0d040bfa 100644 --- a/src/serde_impls.rs +++ b/src/serde_impls.rs @@ -1,6 +1,6 @@ use { crate::{TextRange, TextSize}, - serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}, + serde::{de, Deserialize, Deserializer, Serialize, Serializer}, }; impl Serialize for TextSize { @@ -31,13 +31,14 @@ impl Serialize for TextRange { } impl<'de> Deserialize<'de> for TextRange { + #[allow(clippy::nonminimal_bool)] fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let (start, end) = Deserialize::deserialize(deserializer)?; if !(start <= end) { - return Err(Error::custom(format!( + return Err(de::Error::custom(format!( "invalid range: {:?}..{:?}", start, end ))); From 4b7dd1a15dacb2e85158f64c85e26eba047805b2 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Sat, 14 Mar 2020 22:17:24 -0400 Subject: [PATCH 36/87] Don't silently wrap for too-large str --- src/traits.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/traits.rs b/src/traits.rs index d90f3d431c..7bfb586bad 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -8,14 +8,9 @@ pub trait TextSized: Copy { impl TextSized for &'_ str { fn text_size(self) -> TextSize { - let len = self.len(); - if let Ok(size) = len.try_into() { - size - } else if cfg!(debug_assertions) { - panic!("overflow when converting to TextSize"); - } else { - TextSize(len as u32) - } + self.len() + .try_into() + .unwrap_or_else(|| panic!("string too large ({}) for TextSize", self.len())) } } From 0467db52ed016fa17f1f8d7665d90b475ab865f6 Mon Sep 17 00:00:00 2001 From: Christopher Durham Date: Sun, 15 Mar 2020 11:15:37 -0400 Subject: [PATCH 37/87] Add missing _ --- src/traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traits.rs b/src/traits.rs index 7bfb586bad..ca4b7d9b42 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -10,7 +10,7 @@ impl TextSized for &'_ str { fn text_size(self) -> TextSize { self.len() .try_into() - .unwrap_or_else(|| panic!("string too large ({}) for TextSize", self.len())) + .unwrap_or_else(|_| panic!("string too large ({}) for TextSize", self.len())) } } From 8d19701f6c6a673256bbf44f354183c147580e72 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Fri, 13 Mar 2020 18:29:22 -0400 Subject: [PATCH 38/87] minor improvements everywhere --- src/lib.rs | 3 +++ src/range.rs | 64 +++++++++++++++++++++++++++++++++++----------------- src/size.rs | 30 ++++++++++++++++-------- 3 files changed, 66 insertions(+), 31 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 32262de12c..e194e2317b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,3 +13,6 @@ mod traits; mod serde_impls; pub use crate::{range::TextRange, size::TextSize, traits::TextSized}; + +#[cfg(target_pointer_width = "16")] +compile_error!("text-size assumes usize >= u32 and does not work on 16-bit targets"); diff --git a/src/range.rs b/src/range.rs index df5296af14..35c487afed 100644 --- a/src/range.rs +++ b/src/range.rs @@ -2,30 +2,25 @@ use { crate::TextSize, std::{ cmp, fmt, - ops::{Bound, Index, IndexMut, Range, RangeBounds}, + ops::{Bound, Index, IndexMut, Range, RangeBounds, RangeFrom}, }, }; /// A range in text, represented as a pair of [`TextSize`][struct@TextSize]. /// -/// It is a logical error to have `end() < start()`, but -/// code must not assume this is true for `unsafe` guarantees. -/// /// # Translation from `text_unit` /// -/// - `TextRange::from_to(from, to)` ⟹ `TextRange::from(from..to)` -/// - `TextRange::offset_len(offset, size)` ⟹ `TextRange::from(offset..offset + size)` +/// - `TextRange::from_to(from, to)` ⟹ `TextRange(from, to)` +/// - `TextRange::offset_len(offset, size)` ⟹ `TextRange::to(size).offset(offset)` /// - `range.start()` ⟹ `range.start()` /// - `range.end()` ⟹ `range.end()` -/// - `range.len()` ⟹ `range.len()` +/// - `range.len()` ⟹ `range.len()` /// - `range.is_empty()` ⟹ `range.is_empty()` -/// - `a.is_subrange(b)` ⟹ `b.contains(a)` +/// - `a.is_subrange(b)` ⟹ `b.contains_range(a)` /// - `a.intersection(b)` ⟹ `TextRange::intersection(a, b)` /// - `a.extend_to(b)` ⟹ `TextRange::covering(a, b)` -/// - `range.contains(offset)` ⟹ `range.contains_exclusive(point)` +/// - `range.contains(offset)` ⟹ `range.contains(point)` /// - `range.contains_inclusive(offset)` ⟹ `range.contains_inclusive(point)` -/// -/// † See the note on [`TextRange::len`] for differing behavior for incorrect reverse ranges. #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct TextRange { // Invariant: start <= end @@ -39,7 +34,7 @@ impl fmt::Debug for TextRange { } } -/// Creates a new `TextRange` with given `start` and `end. +/// Creates a new `TextRange` with the given `start` and `end` (`start..end`). /// /// # Panics /// @@ -50,16 +45,47 @@ pub fn TextRange(start: TextSize, end: TextSize) -> TextRange { TextRange { start, end } } -/// Identity methods. impl TextRange { - /// Creates a zero-length range at the specified offset. - pub const fn empty(self, offset: TextSize) -> TextRange { + /// Create a zero-length range at the specified offset (`offset..offset`). + pub const fn empty(offset: TextSize) -> TextRange { TextRange { start: offset, end: offset, } } + /// Create a range up to the given end (`..end`). + pub const fn before(end: TextSize) -> TextRange { + TextRange { + start: TextSize::zero(), + end, + } + } + + /// Create a range after the given start (`start..`). + /// + /// This returns a std [`RangeFrom`] rather than `TextRange` because + /// `TextRange` does not support right-unbounded ranges. As such, this + /// should only be used for direct indexing, and bounded ranges should be + /// used for persistent ranges (`TextRange(start, TextSize::of(text))`). + pub const fn after(start: TextSize) -> RangeFrom { + start.raw as usize.. + } + + /// Offset this range by some amount. + /// + /// This is typically used to convert a range from one coordinate space to + /// another, such as from within a substring to within an entire document. + pub fn offset(self, offset: TextSize) -> TextRange { + TextRange( + self.start().checked_add(offset).unwrap(), + self.end().checked_add(offset).unwrap(), + ) + } +} + +/// Identity methods. +impl TextRange { /// The start point of this range. pub const fn start(self) -> TextSize { self.start @@ -76,10 +102,7 @@ impl TextRange { TextSize(self.end().raw - self.start().raw) } - /// Check if this range empty or reversed. - /// - /// When `end() < start()`, this returns false. - /// Code should prefer `is_empty()` to `len() == 0`. + /// Check if this range is empty. pub const fn is_empty(self) -> bool { // HACK for const fn: math on primitives only self.start().raw == self.end().raw @@ -99,8 +122,7 @@ impl TextRange { /// /// The end index is considered included. pub fn contains_inclusive(self, offset: TextSize) -> bool { - let point = offset.into(); - self.start() <= point && point <= self.end() + self.start() <= offset && offset <= self.end() } /// Check if this range completely contains another range. diff --git a/src/size.rs b/src/size.rs index 454327a015..5b435e896c 100644 --- a/src/size.rs +++ b/src/size.rs @@ -11,15 +11,23 @@ use { /// A measure of text length. Also, equivalently, an index into text. /// -/// This is a utf8-bytes-offset stored as `u32`, but +/// This is a UTF-8 bytes offset stored as `u32`, but /// most clients should treat it as an opaque measure. /// +/// For cases that need to escape `TextSize` and return to working directly +/// with primitive integers, `TextSize` can be converted losslessly to/from +/// `u32` via [`From`] conversions as well as losslessly be converted [`Into`] +/// `usize`. The `usize -> TextSize` direction can be done via [`TryFrom`]. +/// +/// These escape hatches are primarily required for unit testing and when +/// converting from UTF-8 size to another coordinate space, such as UTF-16. +/// /// # Translation from `text_unit` /// /// - `TextUnit::of_char(c)` ⟹ `TextSize::of(c)` -/// - `TextUnit::of_str(s)` ⟹ `TextSize:of(s)` +/// - `TextUnit::of_str(s)` ⟹ `TextSize::of(s)` /// - `TextUnit::from_usize(size)` ⟹ `TextSize::try_from(size).unwrap_or_else(|| panic!(_))` -/// - `unit.to_usize()` ⟹ `usize::try_from(size).unwrap_or_else(|| panic!(_))` +/// - `unit.to_usize()` ⟹ `usize::from(size)` #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct TextSize { pub(crate) raw: u32, @@ -49,6 +57,11 @@ impl TextSize { pub const fn zero() -> TextSize { TextSize(0) } + + /// A size of one. + pub const fn one() -> TextSize { + TextSize(1) + } } /// Methods to act like a primitive integer type, where reasonably applicable. @@ -58,6 +71,8 @@ impl TextSize { pub const MIN: TextSize = TextSize(u32::MIN); /// The largest representable text size. (`u32::MAX`) pub const MAX: TextSize = TextSize(u32::MAX); + /// The text size of a single ASCII character. + pub const ONE: TextSize = TextSize(1); #[allow(missing_docs)] pub fn checked_add(self, rhs: TextSize) -> Option { @@ -72,7 +87,7 @@ impl TextSize { impl From for TextSize { fn from(raw: u32) -> Self { - TextSize { raw } + TextSize(raw) } } @@ -91,12 +106,7 @@ impl TryFrom for TextSize { impl From for usize { fn from(value: TextSize) -> Self { - assert_lossless_conversion(); - return value.raw as usize; - - const fn assert_lossless_conversion() { - [()][(std::mem::size_of::() < std::mem::size_of::()) as usize] - } + value.raw as usize } } From f9d70057bcd32fd290fdcc2e26a9ad712a12fd20 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Sat, 14 Mar 2020 22:14:43 -0400 Subject: [PATCH 39/87] Add #[inline] to most things --- src/range.rs | 12 ++++++++++++ src/size.rs | 19 +++++++++++++++++-- src/traits.rs | 4 ++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/range.rs b/src/range.rs index 35c487afed..6484d2ea34 100644 --- a/src/range.rs +++ b/src/range.rs @@ -40,6 +40,7 @@ impl fmt::Debug for TextRange { /// /// Panics if `end < start`. #[allow(non_snake_case)] +#[inline] pub fn TextRange(start: TextSize, end: TextSize) -> TextRange { assert!(start <= end); TextRange { start, end } @@ -47,6 +48,7 @@ pub fn TextRange(start: TextSize, end: TextSize) -> TextRange { impl TextRange { /// Create a zero-length range at the specified offset (`offset..offset`). + #[inline] pub const fn empty(offset: TextSize) -> TextRange { TextRange { start: offset, @@ -55,6 +57,7 @@ impl TextRange { } /// Create a range up to the given end (`..end`). + #[inline] pub const fn before(end: TextSize) -> TextRange { TextRange { start: TextSize::zero(), @@ -68,6 +71,7 @@ impl TextRange { /// `TextRange` does not support right-unbounded ranges. As such, this /// should only be used for direct indexing, and bounded ranges should be /// used for persistent ranges (`TextRange(start, TextSize::of(text))`). + #[inline] pub const fn after(start: TextSize) -> RangeFrom { start.raw as usize.. } @@ -76,6 +80,7 @@ impl TextRange { /// /// This is typically used to convert a range from one coordinate space to /// another, such as from within a substring to within an entire document. + #[inline] pub fn offset(self, offset: TextSize) -> TextRange { TextRange( self.start().checked_add(offset).unwrap(), @@ -87,22 +92,26 @@ impl TextRange { /// Identity methods. impl TextRange { /// The start point of this range. + #[inline] pub const fn start(self) -> TextSize { self.start } /// The end point of this range. + #[inline] pub const fn end(self) -> TextSize { self.end } /// The size of this range. + #[inline] pub const fn len(self) -> TextSize { // HACK for const fn: math on primitives only TextSize(self.end().raw - self.start().raw) } /// Check if this range is empty. + #[inline] pub const fn is_empty(self) -> bool { // HACK for const fn: math on primitives only self.start().raw == self.end().raw @@ -151,12 +160,14 @@ impl TextRange { impl Index for str { type Output = str; + #[inline] fn index(&self, index: TextRange) -> &Self::Output { &self[Range::::from(index)] } } impl IndexMut for str { + #[inline] fn index_mut(&mut self, index: TextRange) -> &mut Self::Output { &mut self[Range::::from(index)] } @@ -176,6 +187,7 @@ impl From for Range where T: From, { + #[inline] fn from(r: TextRange) -> Self { r.start().into()..r.end().into() } diff --git a/src/size.rs b/src/size.rs index 5b435e896c..4436b9f6f2 100644 --- a/src/size.rs +++ b/src/size.rs @@ -46,6 +46,7 @@ impl fmt::Debug for TextSize { impl TextSize { /// The text size of some text-like object. + #[inline] pub fn of(text: impl TextSized) -> TextSize { text.text_size() } @@ -54,11 +55,13 @@ impl TextSize { /// /// This is equivalent to `TextSize::default()` or [`TextSize::MIN`], /// but is more explicit on intent. + #[inline] pub const fn zero() -> TextSize { TextSize(0) } /// A size of one. + #[inline] pub const fn one() -> TextSize { TextSize(1) } @@ -74,24 +77,28 @@ impl TextSize { /// The text size of a single ASCII character. pub const ONE: TextSize = TextSize(1); - #[allow(missing_docs)] + /// Checked addition. Returns `None` if overflow occurred. + #[inline] pub fn checked_add(self, rhs: TextSize) -> Option { self.raw.checked_add(rhs.raw).map(TextSize) } - #[allow(missing_docs)] + /// Checked subtraction. Returns `None` if overflow occurred. + #[inline] pub fn checked_sub(self, rhs: TextSize) -> Option { self.raw.checked_sub(rhs.raw).map(TextSize) } } impl From for TextSize { + #[inline] fn from(raw: u32) -> Self { TextSize(raw) } } impl From for u32 { + #[inline] fn from(value: TextSize) -> Self { value.raw } @@ -99,12 +106,14 @@ impl From for u32 { impl TryFrom for TextSize { type Error = TryFromIntError; + #[inline] fn try_from(value: usize) -> Result { Ok(u32::try_from(value)?.into()) } } impl From for usize { + #[inline] fn from(value: TextSize) -> Self { value.raw as usize } @@ -114,12 +123,14 @@ macro_rules! ops { (impl $Op:ident for TextSize by fn $f:ident = $op:tt) => { impl $Op for TextSize { type Output = TextSize; + #[inline] fn $f(self, other: TextSize) -> TextSize { TextSize(self.raw $op other.raw) } } impl $Op<&TextSize> for TextSize { type Output = TextSize; + #[inline] fn $f(self, other: &TextSize) -> TextSize { self $op *other } @@ -129,6 +140,7 @@ macro_rules! ops { TextSize: $Op, { type Output = TextSize; + #[inline] fn $f(self, other: T) -> TextSize { *self $op other } @@ -143,6 +155,7 @@ impl AddAssign for TextSize where TextSize: Add, { + #[inline] fn add_assign(&mut self, rhs: A) { *self = *self + rhs } @@ -152,6 +165,7 @@ impl SubAssign for TextSize where TextSize: Sub, { + #[inline] fn sub_assign(&mut self, rhs: S) { *self = *self - rhs } @@ -161,6 +175,7 @@ impl iter::Sum for TextSize where TextSize: Add, { + #[inline] fn sum>(iter: I) -> TextSize { iter.fold(TextSize::zero(), Add::add) } diff --git a/src/traits.rs b/src/traits.rs index ca4b7d9b42..fac1eccdc6 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -6,7 +6,10 @@ pub trait TextSized: Copy { fn text_size(self) -> TextSize; } +/// This will panic for strings larger than `TextSize::MAX` when +/// debug assertions are enabled, and wrap when they are disabled. impl TextSized for &'_ str { + #[inline] fn text_size(self) -> TextSize { self.len() .try_into() @@ -15,6 +18,7 @@ impl TextSized for &'_ str { } impl TextSized for char { + #[inline] fn text_size(self) -> TextSize { TextSize(self.len_utf8() as u32) } From 283cb688d500cfa1f684784b8523864fb3a33afc Mon Sep 17 00:00:00 2001 From: CAD97 Date: Wed, 18 Mar 2020 17:22:00 -0400 Subject: [PATCH 40/87] replace `one` with `ascii` --- src/size.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/size.rs b/src/size.rs index 4436b9f6f2..ed4e849d51 100644 --- a/src/size.rs +++ b/src/size.rs @@ -59,12 +59,6 @@ impl TextSize { pub const fn zero() -> TextSize { TextSize(0) } - - /// A size of one. - #[inline] - pub const fn one() -> TextSize { - TextSize(1) - } } /// Methods to act like a primitive integer type, where reasonably applicable. @@ -75,7 +69,7 @@ impl TextSize { /// The largest representable text size. (`u32::MAX`) pub const MAX: TextSize = TextSize(u32::MAX); /// The text size of a single ASCII character. - pub const ONE: TextSize = TextSize(1); + pub const ASCII: TextSize = TextSize(1); /// Checked addition. Returns `None` if overflow occurred. #[inline] From 9995dccc5c6863dc5cb276e4c00039fac09252b3 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Wed, 18 Mar 2020 17:23:37 -0400 Subject: [PATCH 41/87] remove TextRange::after --- src/range.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/range.rs b/src/range.rs index 6484d2ea34..507de3a5fb 100644 --- a/src/range.rs +++ b/src/range.rs @@ -11,7 +11,7 @@ use { /// # Translation from `text_unit` /// /// - `TextRange::from_to(from, to)` ⟹ `TextRange(from, to)` -/// - `TextRange::offset_len(offset, size)` ⟹ `TextRange::to(size).offset(offset)` +/// - `TextRange::offset_len(offset, size)` ⟹ `TextRange::before(size).offset(offset)` /// - `range.start()` ⟹ `range.start()` /// - `range.end()` ⟹ `range.end()` /// - `range.len()` ⟹ `range.len()` @@ -65,17 +65,6 @@ impl TextRange { } } - /// Create a range after the given start (`start..`). - /// - /// This returns a std [`RangeFrom`] rather than `TextRange` because - /// `TextRange` does not support right-unbounded ranges. As such, this - /// should only be used for direct indexing, and bounded ranges should be - /// used for persistent ranges (`TextRange(start, TextSize::of(text))`). - #[inline] - pub const fn after(start: TextSize) -> RangeFrom { - start.raw as usize.. - } - /// Offset this range by some amount. /// /// This is typically used to convert a range from one coordinate space to From a3188565bda4fd975d03f310a8f0b0ef1ccfb407 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Thu, 19 Mar 2020 11:29:36 -0400 Subject: [PATCH 42/87] rename TextRange::before to up_to --- src/range.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/range.rs b/src/range.rs index 507de3a5fb..08d8d3e2cc 100644 --- a/src/range.rs +++ b/src/range.rs @@ -11,7 +11,7 @@ use { /// # Translation from `text_unit` /// /// - `TextRange::from_to(from, to)` ⟹ `TextRange(from, to)` -/// - `TextRange::offset_len(offset, size)` ⟹ `TextRange::before(size).offset(offset)` +/// - `TextRange::offset_len(offset, size)` ⟹ `TextRange::up_to(size).offset(offset)` /// - `range.start()` ⟹ `range.start()` /// - `range.end()` ⟹ `range.end()` /// - `range.len()` ⟹ `range.len()` @@ -58,7 +58,7 @@ impl TextRange { /// Create a range up to the given end (`..end`). #[inline] - pub const fn before(end: TextSize) -> TextRange { + pub const fn up_to(end: TextSize) -> TextRange { TextRange { start: TextSize::zero(), end, From 0fe47a3efbbefb063fdd0080b57f9656453ba2ff Mon Sep 17 00:00:00 2001 From: CAD97 Date: Thu, 19 Mar 2020 11:29:48 -0400 Subject: [PATCH 43/87] Remove incorrect doc comment --- src/traits.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/traits.rs b/src/traits.rs index fac1eccdc6..8d197db8c1 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -6,8 +6,6 @@ pub trait TextSized: Copy { fn text_size(self) -> TextSize; } -/// This will panic for strings larger than `TextSize::MAX` when -/// debug assertions are enabled, and wrap when they are disabled. impl TextSized for &'_ str { #[inline] fn text_size(self) -> TextSize { From 9d2b3c9a9ae4331ddb871be755e7c429e2c0fe6d Mon Sep 17 00:00:00 2001 From: CAD97 Date: Thu, 19 Mar 2020 11:30:40 -0400 Subject: [PATCH 44/87] Remove TextSize::ASCII --- src/size.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/size.rs b/src/size.rs index ed4e849d51..5239ea6691 100644 --- a/src/size.rs +++ b/src/size.rs @@ -68,8 +68,6 @@ impl TextSize { pub const MIN: TextSize = TextSize(u32::MIN); /// The largest representable text size. (`u32::MAX`) pub const MAX: TextSize = TextSize(u32::MAX); - /// The text size of a single ASCII character. - pub const ASCII: TextSize = TextSize(1); /// Checked addition. Returns `None` if overflow occurred. #[inline] From a9ed5d3fe6158230cc5d3332a4e03b0eee735f26 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Thu, 19 Mar 2020 11:35:52 -0400 Subject: [PATCH 45/87] Remove TextRange::offset (for now?) --- src/range.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/range.rs b/src/range.rs index 08d8d3e2cc..339acf8599 100644 --- a/src/range.rs +++ b/src/range.rs @@ -64,18 +64,6 @@ impl TextRange { end, } } - - /// Offset this range by some amount. - /// - /// This is typically used to convert a range from one coordinate space to - /// another, such as from within a substring to within an entire document. - #[inline] - pub fn offset(self, offset: TextSize) -> TextRange { - TextRange( - self.start().checked_add(offset).unwrap(), - self.end().checked_add(offset).unwrap(), - ) - } } /// Identity methods. From a55a2f525fc818df935809503aaaf75e121f4e80 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Thu, 19 Mar 2020 12:38:54 -0400 Subject: [PATCH 46/87] Remove unneeded import --- src/range.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/range.rs b/src/range.rs index 339acf8599..789d300665 100644 --- a/src/range.rs +++ b/src/range.rs @@ -2,7 +2,7 @@ use { crate::TextSize, std::{ cmp, fmt, - ops::{Bound, Index, IndexMut, Range, RangeBounds, RangeFrom}, + ops::{Bound, Index, IndexMut, Range, RangeBounds}, }, }; From af394662df6542e4213cde0af76efb50ff1762c9 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Thu, 19 Mar 2020 13:51:01 -0400 Subject: [PATCH 47/87] Add ops for TextRange --- src/range.rs | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/src/range.rs b/src/range.rs index 789d300665..e1a7bdf867 100644 --- a/src/range.rs +++ b/src/range.rs @@ -2,7 +2,7 @@ use { crate::TextSize, std::{ cmp, fmt, - ops::{Bound, Index, IndexMut, Range, RangeBounds}, + ops::{Add, AddAssign, Bound, Index, IndexMut, Range, RangeBounds, Sub, SubAssign}, }, }; @@ -133,6 +133,38 @@ impl TextRange { let end = cmp::max(lhs.end(), rhs.end()); TextRange(start, end) } + + /// Add an offset to this range. + /// + /// Note that this is not appropriate for changing where a `TextRange` is + /// within some string; rather, it is for changing the reference anchor + /// that the `TextRange` is measured against. + /// + /// The unchecked version (`Add::add`) will _always_ panic on overflow, + /// in contrast to primitive integers, which check in debug mode only. + #[inline] + pub fn checked_add(self, offset: TextSize) -> Option { + Some(TextRange { + start: self.start.checked_add(offset)?, + end: self.end.checked_add(offset)?, + }) + } + + /// Subtract an offset from this range. + /// + /// Note that this is not appropriate for changing where a `TextRange` is + /// within some string; rather, it is for changing the reference anchor + /// that the `TextRange` is measured against. + /// + /// The unchecked version (`Sub::sub`) will _always_ panic on overflow, + /// in contrast to primitive integers, which check in debug mode only. + #[inline] + pub fn checked_sub(self, offset: TextSize) -> Option { + Some(TextRange { + start: self.start.checked_sub(offset)?, + end: self.end.checked_sub(offset)?, + }) + } } impl Index for str { @@ -169,3 +201,66 @@ where r.start().into()..r.end().into() } } + +macro_rules! ops { + (impl $Op:ident for TextRange by fn $f:ident = $op:tt) => { + impl $Op<&TextSize> for TextRange { + type Output = TextRange; + #[inline] + fn $f(self, other: &TextSize) -> TextRange { + self $op *other + } + } + impl $Op for &TextRange + where + TextRange: $Op, + { + type Output = TextRange; + #[inline] + fn $f(self, other: T) -> TextRange { + *self $op other + } + } + }; +} + +impl Add for TextRange { + type Output = TextRange; + #[inline] + fn add(self, offset: TextSize) -> TextRange { + self.checked_add(offset) + .expect("TextRange +offset overflowed") + } +} + +impl Sub for TextRange { + type Output = TextRange; + #[inline] + fn sub(self, offset: TextSize) -> TextRange { + self.checked_sub(offset) + .expect("TextRange -offset overflowed") + } +} + +ops!(impl Add for TextRange by fn add = +); +ops!(impl Sub for TextRange by fn sub = -); + +impl AddAssign for TextRange +where + TextRange: Add, +{ + #[inline] + fn add_assign(&mut self, rhs: A) { + *self = *self + rhs + } +} + +impl SubAssign for TextRange +where + TextRange: Sub, +{ + #[inline] + fn sub_assign(&mut self, rhs: S) { + *self = *self - rhs + } +} From e05750ee572df39a042d006d7931fca2f8cf8fab Mon Sep 17 00:00:00 2001 From: CAD97 Date: Thu, 19 Mar 2020 13:55:11 -0400 Subject: [PATCH 48/87] correct translation table --- src/range.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/range.rs b/src/range.rs index e1a7bdf867..ebafbc7140 100644 --- a/src/range.rs +++ b/src/range.rs @@ -11,7 +11,7 @@ use { /// # Translation from `text_unit` /// /// - `TextRange::from_to(from, to)` ⟹ `TextRange(from, to)` -/// - `TextRange::offset_len(offset, size)` ⟹ `TextRange::up_to(size).offset(offset)` +/// - `TextRange::offset_len(offset, size)` ⟹ `TextRange::up_to(size) + offset` /// - `range.start()` ⟹ `range.start()` /// - `range.end()` ⟹ `range.end()` /// - `range.len()` ⟹ `range.len()` From e8dcd41140f6cdd30082d4c535dd0d5c3fbdc084 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 20 Mar 2020 09:34:24 +0100 Subject: [PATCH 49/87] Make intersect and cover into methods --- src/range.rs | 19 ++++++++++++------- tests/main.rs | 31 +++++++++++++++++-------------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/range.rs b/src/range.rs index ebafbc7140..bc899e71a0 100644 --- a/src/range.rs +++ b/src/range.rs @@ -118,22 +118,27 @@ impl TextRange { /// The range covered by both ranges, if it exists. /// If the ranges touch but do not overlap, the output range is empty. - pub fn intersection(lhs: TextRange, rhs: TextRange) -> Option { - let start = cmp::max(lhs.start(), rhs.start()); - let end = cmp::min(lhs.end(), rhs.end()); + pub fn intersect(self, other: TextRange) -> Option { + let start = cmp::max(self.start(), other.start()); + let end = cmp::min(self.end(), other.end()); if end < start { return None; } Some(TextRange(start, end)) } - /// The smallest range that completely contains both ranges. - pub fn covering(lhs: TextRange, rhs: TextRange) -> TextRange { - let start = cmp::min(lhs.start(), rhs.start()); - let end = cmp::max(lhs.end(), rhs.end()); + /// Extends the range to cover `other` as well. + pub fn cover(self, other: TextRange) -> TextRange { + let start = cmp::min(self.start(), other.start()); + let end = cmp::max(self.end(), other.end()); TextRange(start, end) } + /// Extends the range to cover `other` offsets as well. + pub fn cover_offset(self, other: TextSize) -> TextRange { + self.cover(TextRange::empty(other)) + } + /// Add an offset to this range. /// /// Note that this is not appropriate for changing where a `TextRange` is diff --git a/tests/main.rs b/tests/main.rs index 073c2ebaa3..5a9e678618 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -37,23 +37,26 @@ fn contains() { } #[test] -fn intersection() { - assert_eq!( - TextRange::intersection(range(1..2), range(2..3)), - Some(range(2..2)) - ); - assert_eq!( - TextRange::intersection(range(1..5), range(2..3)), - Some(range(2..3)) - ); - assert_eq!(TextRange::intersection(range(1..2), range(3..4)), None); +fn intersect() { + assert_eq!(range(1..2).intersect(range(2..3)), Some(range(2..2))); + assert_eq!(range(1..5).intersect(range(2..3)), Some(range(2..3))); + assert_eq!(range(1..2).intersect(range(3..4)), None); } #[test] -fn covering() { - assert_eq!(TextRange::covering(range(1..2), range(2..3)), range(1..3)); - assert_eq!(TextRange::covering(range(1..5), range(2..3)), range(1..5)); - assert_eq!(TextRange::covering(range(1..2), range(4..5)), range(1..5)); +fn cover() { + assert_eq!(range(1..2).cover(range(2..3)), range(1..3)); + assert_eq!(range(1..5).cover(range(2..3)), range(1..5)); + assert_eq!(range(1..2).cover(range(4..5)), range(1..5)); +} + +#[test] +fn cover_offset() { + assert_eq!(range(1..3).cover_offset(size(0)), range(0..3)); + assert_eq!(range(1..3).cover_offset(size(1)), range(1..3)); + assert_eq!(range(1..3).cover_offset(size(2)), range(1..3)); + assert_eq!(range(1..3).cover_offset(size(3)), range(1..3)); + assert_eq!(range(1..3).cover_offset(size(4)), range(1..4)); } #[test] From f2eeea642ff1ef247748b37d34fe33db71e561ae Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 20 Mar 2020 17:12:32 +0100 Subject: [PATCH 50/87] Rename repo --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 95c11472cd..08f5d4f719 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,8 @@ authors = [ ] description = "Newtypes for text offsets" license = "MIT OR Apache-2.0" -repository = "https://github.com/matklad/text_unit" -documentation = "https://docs.rs/text_unit" +repository = "https://github.com/rust-analyzer/text-size" +documentation = "https://docs.rs/text-size" [dependencies] serde = { version = "1.0", optional = true, default_features = false } From 3bae1bdc8043d81f8b30543e90996f7ef4454125 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 20 Mar 2020 17:17:40 +0100 Subject: [PATCH 51/87] Swithc to GitHub actions --- .github/workflows/ci.yaml | 36 ++++++++++++++++++++++++++++++++++++ .travis.yml | 2 -- bors.toml | 6 ++++++ 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yaml delete mode 100644 .travis.yml create mode 100644 bors.toml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000000..aa0587bd98 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,36 @@ +name: CI +on: + pull_request: + push: + branches: + - master + - staging + - trying + +jobs: + rust: + name: Rust + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + env: + RUSTFLAGS: -D warnings + RUSTUP_MAX_RETRIES: 10 + CARGO_NET_RETRY: 10 + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + + - name: Test + run: cargo test --features serde diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a7d0a0bc81..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,2 +0,0 @@ -language: rust - diff --git a/bors.toml b/bors.toml new file mode 100644 index 0000000000..932be8d090 --- /dev/null +++ b/bors.toml @@ -0,0 +1,6 @@ +status = [ + "Rust (ubuntu-latest)", + "Rust (windows-latest)", + "Rust (macos-latest)", +] +delete_merged_branches = true From 8c16c6a3582fa8b99ab1db32f742715cc646bb32 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 21 Mar 2020 00:43:20 +0100 Subject: [PATCH 52/87] Update src/range.rs Co-Authored-By: Christopher Durham --- src/range.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/range.rs b/src/range.rs index bc899e71a0..b52a64af39 100644 --- a/src/range.rs +++ b/src/range.rs @@ -135,8 +135,8 @@ impl TextRange { } /// Extends the range to cover `other` offsets as well. - pub fn cover_offset(self, other: TextSize) -> TextRange { - self.cover(TextRange::empty(other)) + pub fn cover_offset(self, offset: TextSize) -> TextRange { + self.cover(TextRange::empty(offset)) } /// Add an offset to this range. From d789405d93faedd6e4514d0e360aa4cdca050d05 Mon Sep 17 00:00:00 2001 From: Christopher Durham Date: Fri, 20 Mar 2020 20:21:44 -0400 Subject: [PATCH 53/87] Add licensing text to README --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 6887ccf032..482a055fc3 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,20 @@ A library that provides newtype wrappers for `u32` and `(u32, u32)` for use as text offsets. See the [docs](https://docs.rs/text_unit/) for more. + +## License + +Licensed under either of + + * Apache License, Version 2.0 + ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license + ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. From 6d75655e17cb1cfbd91f271cb0bbdfb18e3972ec Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 21 Mar 2020 09:15:28 +0100 Subject: [PATCH 54/87] Make TextRange constructors more boring Remove `fn TextRange(` as that's slightly unusual and surprising, which will add up to a lot of confusion over the long run. Instead add: * `new` as the biased, canonical way to create range from bounds * `from_len` as an alternative ctor from starting position and len * `empty` for empty ranges at a given offset * `up_to` for ranges at zero offset with given length * `default` for an empty range at zero --- src/range.rs | 53 +++++++++++++++++++++++++--------------------- src/serde_impls.rs | 4 ++-- src/size.rs | 19 ++++++----------- src/traits.rs | 2 +- tests/main.rs | 2 +- tests/serde.rs | 2 +- 6 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/range.rs b/src/range.rs index b52a64af39..0e5809114a 100644 --- a/src/range.rs +++ b/src/range.rs @@ -10,18 +10,18 @@ use { /// /// # Translation from `text_unit` /// -/// - `TextRange::from_to(from, to)` ⟹ `TextRange(from, to)` -/// - `TextRange::offset_len(offset, size)` ⟹ `TextRange::up_to(size) + offset` +/// - `TextRange::from_to(from, to)` ⟹ `TextRange::new(from, to)` +/// - `TextRange::offset_len(offset, size)` ⟹ `TextRange::from_len(offset, size)` /// - `range.start()` ⟹ `range.start()` /// - `range.end()` ⟹ `range.end()` /// - `range.len()` ⟹ `range.len()` /// - `range.is_empty()` ⟹ `range.is_empty()` /// - `a.is_subrange(b)` ⟹ `b.contains_range(a)` -/// - `a.intersection(b)` ⟹ `TextRange::intersection(a, b)` -/// - `a.extend_to(b)` ⟹ `TextRange::covering(a, b)` +/// - `a.intersection(b)` ⟹ `a.intersect(b)` +/// - `a.extend_to(b)` ⟹ `a.cover(b)` /// - `range.contains(offset)` ⟹ `range.contains(point)` /// - `range.contains_inclusive(offset)` ⟹ `range.contains_inclusive(point)` -#[derive(Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] pub struct TextRange { // Invariant: start <= end start: TextSize, @@ -34,19 +34,24 @@ impl fmt::Debug for TextRange { } } -/// Creates a new `TextRange` with the given `start` and `end` (`start..end`). -/// -/// # Panics -/// -/// Panics if `end < start`. -#[allow(non_snake_case)] -#[inline] -pub fn TextRange(start: TextSize, end: TextSize) -> TextRange { - assert!(start <= end); - TextRange { start, end } -} - impl TextRange { + /// Creates a new `TextRange` with the given `start` and `end` (`start..end`). + /// + /// # Panics + /// + /// Panics if `end < start`. + #[inline] + pub fn new(start: TextSize, end: TextSize) -> TextRange { + assert!(start <= end); + TextRange { start, end } + } + + /// Create a new `TextRange` with the given `start` and `len` (`start..start + len`). + #[inline] + pub fn from_len(start: TextSize, len: TextSize) -> TextRange { + TextRange::new(start, start + len) + } + /// Create a zero-length range at the specified offset (`offset..offset`). #[inline] pub const fn empty(offset: TextSize) -> TextRange { @@ -59,10 +64,8 @@ impl TextRange { /// Create a range up to the given end (`..end`). #[inline] pub const fn up_to(end: TextSize) -> TextRange { - TextRange { - start: TextSize::zero(), - end, - } + let start = TextSize::zero(); + TextRange { start, end } } } @@ -84,7 +87,9 @@ impl TextRange { #[inline] pub const fn len(self) -> TextSize { // HACK for const fn: math on primitives only - TextSize(self.end().raw - self.start().raw) + TextSize { + raw: self.end().raw - self.start().raw, + } } /// Check if this range is empty. @@ -124,14 +129,14 @@ impl TextRange { if end < start { return None; } - Some(TextRange(start, end)) + Some(TextRange::new(start, end)) } /// Extends the range to cover `other` as well. pub fn cover(self, other: TextRange) -> TextRange { let start = cmp::min(self.start(), other.start()); let end = cmp::max(self.end(), other.end()); - TextRange(start, end) + TextRange::new(start, end) } /// Extends the range to cover `other` offsets as well. diff --git a/src/serde_impls.rs b/src/serde_impls.rs index 6a0d040bfa..a94bee9567 100644 --- a/src/serde_impls.rs +++ b/src/serde_impls.rs @@ -17,7 +17,7 @@ impl<'de> Deserialize<'de> for TextSize { where D: Deserializer<'de>, { - Deserialize::deserialize(deserializer).map(TextSize) + u32::deserialize(deserializer).map(TextSize::from) } } @@ -43,6 +43,6 @@ impl<'de> Deserialize<'de> for TextRange { start, end ))); } - Ok(TextRange(start, end)) + Ok(TextRange::new(start, end)) } } diff --git a/src/size.rs b/src/size.rs index 5239ea6691..14f7b37df0 100644 --- a/src/size.rs +++ b/src/size.rs @@ -33,11 +33,6 @@ pub struct TextSize { pub(crate) raw: u32, } -#[allow(non_snake_case)] -pub(crate) const fn TextSize(raw: u32) -> TextSize { - TextSize { raw } -} - impl fmt::Debug for TextSize { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.raw) @@ -57,7 +52,7 @@ impl TextSize { /// but is more explicit on intent. #[inline] pub const fn zero() -> TextSize { - TextSize(0) + TextSize { raw: 0 } } } @@ -65,27 +60,27 @@ impl TextSize { // Last updated for parity with Rust 1.42.0. impl TextSize { /// The smallest representable text size. (`u32::MIN`) - pub const MIN: TextSize = TextSize(u32::MIN); + pub const MIN: TextSize = TextSize { raw: u32::MIN }; /// The largest representable text size. (`u32::MAX`) - pub const MAX: TextSize = TextSize(u32::MAX); + pub const MAX: TextSize = TextSize { raw: u32::MAX }; /// Checked addition. Returns `None` if overflow occurred. #[inline] pub fn checked_add(self, rhs: TextSize) -> Option { - self.raw.checked_add(rhs.raw).map(TextSize) + self.raw.checked_add(rhs.raw).map(|raw| TextSize { raw }) } /// Checked subtraction. Returns `None` if overflow occurred. #[inline] pub fn checked_sub(self, rhs: TextSize) -> Option { - self.raw.checked_sub(rhs.raw).map(TextSize) + self.raw.checked_sub(rhs.raw).map(|raw| TextSize { raw }) } } impl From for TextSize { #[inline] fn from(raw: u32) -> Self { - TextSize(raw) + TextSize { raw } } } @@ -117,7 +112,7 @@ macro_rules! ops { type Output = TextSize; #[inline] fn $f(self, other: TextSize) -> TextSize { - TextSize(self.raw $op other.raw) + TextSize { raw: self.raw $op other.raw } } } impl $Op<&TextSize> for TextSize { diff --git a/src/traits.rs b/src/traits.rs index 8d197db8c1..7064dbc658 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -18,6 +18,6 @@ impl TextSized for &'_ str { impl TextSized for char { #[inline] fn text_size(self) -> TextSize { - TextSize(self.len_utf8() as u32) + (self.len_utf8() as u32).into() } } diff --git a/tests/main.rs b/tests/main.rs index 5a9e678618..f8eb6d6735 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -5,7 +5,7 @@ fn size(x: u32) -> TextSize { } fn range(x: ops::Range) -> TextRange { - TextRange(x.start.into(), x.end.into()) + TextRange::new(x.start.into(), x.end.into()) } #[test] diff --git a/tests/serde.rs b/tests/serde.rs index a32f89e865..874258a35f 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -5,7 +5,7 @@ fn size(x: u32) -> TextSize { } fn range(x: ops::Range) -> TextRange { - TextRange(x.start.into(), x.end.into()) + TextRange::new(x.start.into(), x.end.into()) } #[test] From 63cbf15486f5aef3a1f59edc232723a317b44b85 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Fri, 20 Mar 2020 19:43:26 -0400 Subject: [PATCH 55/87] add recursive impl for TextSized --- src/traits.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/traits.rs b/src/traits.rs index 7064dbc658..6c30549edc 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,4 +1,7 @@ -use {crate::TextSize, std::convert::TryInto}; +use { + crate::TextSize, + std::{convert::TryInto, ops::Deref}, +}; /// Text-like structures that have a text size. pub trait TextSized: Copy { @@ -15,6 +18,17 @@ impl TextSized for &'_ str { } } +impl TextSized for &'_ D +where + D: Deref, + for<'a> &'a D::Target: TextSized, +{ + #[inline] + fn text_size(self) -> TextSize { + self.deref().text_size() + } +} + impl TextSized for char { #[inline] fn text_size(self) -> TextSize { From be985b7c445e7b26a2d0f85bf1d0f3cd811c2767 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Fri, 20 Mar 2020 20:01:14 -0400 Subject: [PATCH 56/87] target the TextSized blanket impl more tightly" --- src/traits.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/traits.rs b/src/traits.rs index 6c30549edc..6f3462bee5 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -20,8 +20,7 @@ impl TextSized for &'_ str { impl TextSized for &'_ D where - D: Deref, - for<'a> &'a D::Target: TextSized, + D: Deref, { #[inline] fn text_size(self) -> TextSize { From f1250948c1a3f0732a1496af3e19430c05b33d32 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Sat, 21 Mar 2020 19:23:59 -0400 Subject: [PATCH 57/87] Add assertion for TextSized impls --- src/traits.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/traits.rs b/src/traits.rs index 6f3462bee5..018a62cb76 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -34,3 +34,23 @@ impl TextSized for char { (self.len_utf8() as u32).into() } } + +// assertion shape from static_assertions::assert_impl_all! +const _: fn() = || { + use std::borrow::Cow; + + fn assert_impl() {} + + assert_impl::<&String>(); + assert_impl::<&Cow>(); + + struct StringLike {} + impl Deref for StringLike { + type Target = str; + fn deref(&self) -> &str { + unreachable!() + } + } + + assert_impl::<&StringLike>(); +}; From 8a6f5e3cc97f3762ac9841ce870edcee82df1dba Mon Sep 17 00:00:00 2001 From: CAD97 Date: Mon, 23 Mar 2020 16:45:18 -0400 Subject: [PATCH 58/87] Move construction tests into a test --- src/traits.rs | 20 -------------------- tests/constructors.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 20 deletions(-) create mode 100644 tests/constructors.rs diff --git a/src/traits.rs b/src/traits.rs index 018a62cb76..6f3462bee5 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -34,23 +34,3 @@ impl TextSized for char { (self.len_utf8() as u32).into() } } - -// assertion shape from static_assertions::assert_impl_all! -const _: fn() = || { - use std::borrow::Cow; - - fn assert_impl() {} - - assert_impl::<&String>(); - assert_impl::<&Cow>(); - - struct StringLike {} - impl Deref for StringLike { - type Target = str; - fn deref(&self) -> &str { - unreachable!() - } - } - - assert_impl::<&StringLike>(); -}; diff --git a/tests/constructors.rs b/tests/constructors.rs new file mode 100644 index 0000000000..eba587b539 --- /dev/null +++ b/tests/constructors.rs @@ -0,0 +1,31 @@ +use { + std::{borrow::Cow, ops::Deref}, + text_size::*, +}; + +struct StringLike<'a>(&'a str); + +impl Deref for StringLike<'_> { + type Target = str; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[test] +fn main() { + let s = ""; + let _ = TextSize::of(&s); + + let s = String::new(); + let _ = TextSize::of(&s); + + let s = Cow::Borrowed(""); + let _ = TextSize::of(&s); + + let s = Cow::Owned(String::new()); + let _ = TextSize::of(&s); + + let s = StringLike(""); + let _ = TextSize::of(&s); +} From 9ffd8c4afddd461eadac7084dddc466770e8c35e Mon Sep 17 00:00:00 2001 From: CAD97 Date: Mon, 23 Mar 2020 17:03:43 -0400 Subject: [PATCH 59/87] Tests to ensure common and auto trait presence --- Cargo.toml | 1 + tests/auto_traits.rs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 tests/auto_traits.rs diff --git a/Cargo.toml b/Cargo.toml index 08f5d4f719..a0126f7d6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ serde = { version = "1.0", optional = true, default_features = false } [dev-dependencies] serde_test = "1.0" +static_assertions = "1.1" [[test]] name = "serde" diff --git a/tests/auto_traits.rs b/tests/auto_traits.rs new file mode 100644 index 0000000000..6e62369533 --- /dev/null +++ b/tests/auto_traits.rs @@ -0,0 +1,18 @@ +use { + static_assertions::*, + std::{ + fmt::Debug, + hash::Hash, + marker::{Send, Sync}, + panic::{RefUnwindSafe, UnwindSafe}, + }, + text_size::*, +}; + +// auto traits +assert_impl_all!(TextSize: Send, Sync, Unpin, UnwindSafe, RefUnwindSafe); +assert_impl_all!(TextRange: Send, Sync, Unpin, UnwindSafe, RefUnwindSafe); + +// common traits +assert_impl_all!(TextSize: Copy, Debug, Default, Hash, Ord); +assert_impl_all!(TextRange: Copy, Debug, Default, Hash, Eq); From 1d2de420c4c17f917e60464356104a066efd4883 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Mon, 23 Mar 2020 21:30:39 -0400 Subject: [PATCH 60/87] Add doc examples to things --- src/range.rs | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/size.rs | 15 +++++++ 2 files changed, 140 insertions(+) diff --git a/src/range.rs b/src/range.rs index 0e5809114a..2ed608aa53 100644 --- a/src/range.rs +++ b/src/range.rs @@ -8,6 +8,8 @@ use { /// A range in text, represented as a pair of [`TextSize`][struct@TextSize]. /// +/// It is a logic error for `start` to be greater than `end`. +/// /// # Translation from `text_unit` /// /// - `TextRange::from_to(from, to)` ⟹ `TextRange::new(from, to)` @@ -40,6 +42,19 @@ impl TextRange { /// # Panics /// /// Panics if `end < start`. + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// let start = TextSize::from(5); + /// let end = TextSize::from(10); + /// let range = TextRange::new(start, end); + /// + /// assert_eq!(range.start(), start); + /// assert_eq!(range.end(), end); + /// assert_eq!(range.len(), end - start); + /// ``` #[inline] pub fn new(start: TextSize, end: TextSize) -> TextRange { assert!(start <= end); @@ -47,12 +62,37 @@ impl TextRange { } /// Create a new `TextRange` with the given `start` and `len` (`start..start + len`). + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// let text = "0123456789"; + /// + /// let start = TextSize::from(2); + /// let length = TextSize::from(5); + /// let range = TextRange::from_len(start, length); + /// + /// assert_eq!(range, TextRange::new(start, start + length)); + /// assert_eq!(&text[range], "23456") + /// ``` #[inline] pub fn from_len(start: TextSize, len: TextSize) -> TextRange { TextRange::new(start, start + len) } /// Create a zero-length range at the specified offset (`offset..offset`). + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// let point: TextSize; + /// # point = TextSize::from(3); + /// let range = TextRange::empty(point); + /// assert!(range.is_empty()); + /// assert_eq!(range, TextRange::new(point, point)); + /// ``` #[inline] pub const fn empty(offset: TextSize) -> TextRange { TextRange { @@ -62,6 +102,19 @@ impl TextRange { } /// Create a range up to the given end (`..end`). + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// let point: TextSize; + /// # point = TextSize::from(12); + /// let range = TextRange::up_to(point); + /// + /// assert_eq!(range.len(), point); + /// assert_eq!(range, TextRange::new(TextSize::zero(), point)); + /// assert_eq!(range, TextRange::from_len(TextSize::zero(), point)); + /// ``` #[inline] pub const fn up_to(end: TextSize) -> TextRange { let start = TextSize::zero(); @@ -105,6 +158,17 @@ impl TextRange { /// Check if this range contains an offset. /// /// The end index is considered excluded. + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// let (start, end): (TextSize, TextSize); + /// # start = 10.into(); end = 20.into(); + /// let range = TextRange::new(start, end); + /// assert!(range.contains(start)); + /// assert!(!range.contains(end)); + /// ``` pub fn contains(self, offset: TextSize) -> bool { self.start() <= offset && offset < self.end() } @@ -112,17 +176,55 @@ impl TextRange { /// Check if this range contains an offset. /// /// The end index is considered included. + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// let (start, end): (TextSize, TextSize); + /// # start = 10.into(); end = 20.into(); + /// let range = TextRange::new(start, end); + /// assert!(range.contains_inclusive(start)); + /// assert!(range.contains_inclusive(end)); + /// ``` pub fn contains_inclusive(self, offset: TextSize) -> bool { self.start() <= offset && offset <= self.end() } /// Check if this range completely contains another range. + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// let larger = TextRange::new(0.into(), 20.into()); + /// let smaller = TextRange::new(5.into(), 15.into()); + /// assert!(larger.contains_range(smaller)); + /// assert!(!smaller.contains_range(larger)); + /// + /// // a range always contains itself + /// assert!(larger.contains_range(larger)); + /// assert!(smaller.contains_range(smaller)); + /// ``` pub fn contains_range(self, other: TextRange) -> bool { self.start() <= other.start() && other.end() <= self.end() } /// The range covered by both ranges, if it exists. /// If the ranges touch but do not overlap, the output range is empty. + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// assert_eq!( + /// TextRange::intersect( + /// TextRange::new(0.into(), 10.into()), + /// TextRange::new(5.into(), 15.into()), + /// ), + /// Some(TextRange::new(5.into(), 10.into())), + /// ); + /// ``` pub fn intersect(self, other: TextRange) -> Option { let start = cmp::max(self.start(), other.start()); let end = cmp::min(self.end(), other.end()); @@ -133,6 +235,19 @@ impl TextRange { } /// Extends the range to cover `other` as well. + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// assert_eq!( + /// TextRange::cover( + /// TextRange::new(0.into(), 5.into()), + /// TextRange::new(15.into(), 20.into()), + /// ), + /// TextRange::new(0.into(), 20.into()), + /// ); + /// ``` pub fn cover(self, other: TextRange) -> TextRange { let start = cmp::min(self.start(), other.start()); let end = cmp::max(self.end(), other.end()); @@ -140,6 +255,16 @@ impl TextRange { } /// Extends the range to cover `other` offsets as well. + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// assert_eq!( + /// TextRange::empty(TextSize::zero()).cover_offset(20.into()), + /// TextRange::new(0.into(), 20.into()), + /// ) + /// ``` pub fn cover_offset(self, offset: TextSize) -> TextRange { self.cover(TextRange::empty(offset)) } diff --git a/src/size.rs b/src/size.rs index 14f7b37df0..9a0256e8b2 100644 --- a/src/size.rs +++ b/src/size.rs @@ -41,6 +41,21 @@ impl fmt::Debug for TextSize { impl TextSize { /// The text size of some text-like object. + /// + /// Accepts `char`, `&str`, and references to any custom string-like type + /// that dereferences to `str`. Types that don't dereference to `str` but + /// want to be usable in this constructor can implement [`TextSized`]. + /// + /// # Examples + /// + /// ```rust + /// # use text_size::*; + /// let char_size = TextSize::of('🦀'); + /// assert_eq!(char_size, TextSize::from(4)); + /// + /// let str_size = TextSize::of("rust-analyzer"); + /// assert_eq!(str_size, TextSize::from(13)); + /// ``` #[inline] pub fn of(text: impl TextSized) -> TextSize { text.text_size() From 2c39fd6e331f406814ad6fe73a77d67f20c847fe Mon Sep 17 00:00:00 2001 From: CAD97 Date: Mon, 23 Mar 2020 21:31:14 -0400 Subject: [PATCH 61/87] Make sure docs build on CI --- .github/workflows/ci.yaml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index aa0587bd98..1bd88df68e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -33,4 +33,26 @@ jobs: profile: minimal - name: Test - run: cargo test --features serde + run: cargo test --all-features + + rustdoc: + name: Docs + runs-on: ubuntu-latest + + env: + RUSTFLAGS: -D warnings + RUSTUP_MAX_RETRIES: 10 + CARGO_NET_RETRY: 10 + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + profile: minimal + + - name: Rustdoc + run: cargo doc --all-features From 8951ec18f21cc7f4ee2baab2c41541367f4d4d95 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Mon, 23 Mar 2020 21:36:09 -0400 Subject: [PATCH 62/87] TextSized is not meant to be used directly... so rename it to a name more distinct from TextSize. --- src/lib.rs | 2 +- src/size.rs | 6 +++--- src/traits.rs | 18 +++++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e194e2317b..aea1591003 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,7 @@ mod traits; #[cfg(feature = "serde")] mod serde_impls; -pub use crate::{range::TextRange, size::TextSize, traits::TextSized}; +pub use crate::{range::TextRange, size::TextSize, traits::LenTextSize}; #[cfg(target_pointer_width = "16")] compile_error!("text-size assumes usize >= u32 and does not work on 16-bit targets"); diff --git a/src/size.rs b/src/size.rs index 14f7b37df0..16fd7d648d 100644 --- a/src/size.rs +++ b/src/size.rs @@ -1,5 +1,5 @@ use { - crate::TextSized, + crate::LenTextSize, std::{ convert::TryFrom, fmt, iter, @@ -42,8 +42,8 @@ impl fmt::Debug for TextSize { impl TextSize { /// The text size of some text-like object. #[inline] - pub fn of(text: impl TextSized) -> TextSize { - text.text_size() + pub fn of(text: impl LenTextSize) -> TextSize { + text.len_text_size() } /// A size of zero. diff --git a/src/traits.rs b/src/traits.rs index 6f3462bee5..745675fda7 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -4,33 +4,33 @@ use { }; /// Text-like structures that have a text size. -pub trait TextSized: Copy { +pub trait LenTextSize: Copy { /// The size of this text-alike. - fn text_size(self) -> TextSize; + fn len_text_size(self) -> TextSize; } -impl TextSized for &'_ str { +impl LenTextSize for &'_ str { #[inline] - fn text_size(self) -> TextSize { + fn len_text_size(self) -> TextSize { self.len() .try_into() .unwrap_or_else(|_| panic!("string too large ({}) for TextSize", self.len())) } } -impl TextSized for &'_ D +impl LenTextSize for &'_ D where D: Deref, { #[inline] - fn text_size(self) -> TextSize { - self.deref().text_size() + fn len_text_size(self) -> TextSize { + self.deref().len_text_size() } } -impl TextSized for char { +impl LenTextSize for char { #[inline] - fn text_size(self) -> TextSize { + fn len_text_size(self) -> TextSize { (self.len_utf8() as u32).into() } } From e88e4aa7bbc9963c5158c5ac7116fda1a5bca5ce Mon Sep 17 00:00:00 2001 From: CAD97 Date: Wed, 25 Mar 2020 20:05:18 -0400 Subject: [PATCH 63/87] Fix broken intra doc link --- src/size.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/size.rs b/src/size.rs index 234e54d888..e20763f4bc 100644 --- a/src/size.rs +++ b/src/size.rs @@ -44,7 +44,7 @@ impl TextSize { /// /// Accepts `char`, `&str`, and references to any custom string-like type /// that dereferences to `str`. Types that don't dereference to `str` but - /// want to be usable in this constructor can implement [`TextSized`]. + /// want to be usable in this constructor can implement [`LenTextSize`]. /// /// # Examples /// From 9332ee9efba634a2f832103e479b2b767428a70e Mon Sep 17 00:00:00 2001 From: CAD97 Date: Mon, 23 Mar 2020 21:44:08 -0400 Subject: [PATCH 64/87] Just unwrap in LenTextSize for &str --- src/traits.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/traits.rs b/src/traits.rs index 745675fda7..65f6445a1e 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -12,9 +12,7 @@ pub trait LenTextSize: Copy { impl LenTextSize for &'_ str { #[inline] fn len_text_size(self) -> TextSize { - self.len() - .try_into() - .unwrap_or_else(|_| panic!("string too large ({}) for TextSize", self.len())) + self.len().try_into().unwrap() } } From 85935e0026cf93c24f9690234e15440eb3ab8ab7 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Mon, 23 Mar 2020 21:47:35 -0400 Subject: [PATCH 65/87] Use standard generics for TextSize::of There is no specific reason to use APIT here, so prefer the form that allows more control for the user, in the form of the turbofish. --- src/size.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/size.rs b/src/size.rs index e20763f4bc..171f337777 100644 --- a/src/size.rs +++ b/src/size.rs @@ -57,7 +57,7 @@ impl TextSize { /// assert_eq!(str_size, TextSize::from(13)); /// ``` #[inline] - pub fn of(text: impl LenTextSize) -> TextSize { + pub fn of(text: T) -> TextSize { text.len_text_size() } From fdf4345cdda5e3a67bcc152652c7747a895d8298 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Wed, 25 Mar 2020 19:16:20 -0400 Subject: [PATCH 66/87] Actually fail CI for doc warnings --- .github/workflows/ci.yaml | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1bd88df68e..4538ca8479 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -7,6 +7,11 @@ on: - staging - trying +env: + RUSTFLAGS: -D warnings + RUSTUP_MAX_RETRIES: 10 + CARGO_NET_RETRY: 10 + jobs: rust: name: Rust @@ -17,11 +22,6 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - env: - RUSTFLAGS: -D warnings - RUSTUP_MAX_RETRIES: 10 - CARGO_NET_RETRY: 10 - steps: - name: Checkout repository uses: actions/checkout@v2 @@ -39,11 +39,6 @@ jobs: name: Docs runs-on: ubuntu-latest - env: - RUSTFLAGS: -D warnings - RUSTUP_MAX_RETRIES: 10 - CARGO_NET_RETRY: 10 - steps: - name: Checkout repository uses: actions/checkout@v2 @@ -53,6 +48,7 @@ jobs: with: toolchain: nightly profile: minimal + override: true - name: Rustdoc - run: cargo doc --all-features + run: cargo rustdoc --all-features -- -D warnings From aa8e477c16ee597048d7b52137b44f318977aac9 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Wed, 25 Mar 2020 20:39:02 -0400 Subject: [PATCH 67/87] Bump dev version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a0126f7d6d..4ccbfdcb7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "text-size" -version = "0.99.0-dev.2" +version = "0.99.0-dev.3" edition = "2018" authors = [ From 5ef8391b680be9ab20f3a1457c68d9dd5122a16e Mon Sep 17 00:00:00 2001 From: CAD97 Date: Wed, 25 Mar 2020 21:41:48 -0400 Subject: [PATCH 68/87] Re-add Index for String --- src/range.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/range.rs b/src/range.rs index 2ed608aa53..11fe1e702d 100644 --- a/src/range.rs +++ b/src/range.rs @@ -305,14 +305,29 @@ impl TextRange { impl Index for str { type Output = str; #[inline] - fn index(&self, index: TextRange) -> &Self::Output { + fn index(&self, index: TextRange) -> &str { + &self[Range::::from(index)] + } +} + +impl Index for String { + type Output = str; + #[inline] + fn index(&self, index: TextRange) -> &str { &self[Range::::from(index)] } } impl IndexMut for str { #[inline] - fn index_mut(&mut self, index: TextRange) -> &mut Self::Output { + fn index_mut(&mut self, index: TextRange) -> &mut str { + &mut self[Range::::from(index)] + } +} + +impl IndexMut for String { + #[inline] + fn index_mut(&mut self, index: TextRange) -> &mut str { &mut self[Range::::from(index)] } } From daa70c8c73c47cb0ded543f42a913c53651e8dab Mon Sep 17 00:00:00 2001 From: CAD97 Date: Thu, 26 Mar 2020 16:58:49 -0400 Subject: [PATCH 69/87] Don't blanket impl LenTextSize --- src/traits.rs | 47 +++++++++++++++++++++++++++++++++---------- tests/constructors.rs | 41 ++++++++++++++++++++----------------- 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/src/traits.rs b/src/traits.rs index 65f6445a1e..3944030c62 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,6 +1,6 @@ use { crate::TextSize, - std::{convert::TryInto, ops::Deref}, + std::{borrow::Cow, convert::TryInto, rc::Rc, sync::Arc}, }; /// Text-like structures that have a text size. @@ -16,19 +16,44 @@ impl LenTextSize for &'_ str { } } -impl LenTextSize for &'_ D -where - D: Deref, -{ - #[inline] - fn len_text_size(self) -> TextSize { - self.deref().len_text_size() - } -} - impl LenTextSize for char { #[inline] fn len_text_size(self) -> TextSize { (self.len_utf8() as u32).into() } } + +impl LenTextSize for &'_ D +where + D: LenTextSize + Copy, +{ + fn len_text_size(self) -> TextSize { + D::len_text_size(*self) + } +} + +// Because we could not find a smart blanket impl to do this automatically and +// cleanly (rust-analyzer/text-size#36), just provide a bunch of manual impls. +// If a type fits in this macro and you need it to impl LenTextSize, just open +// a PR and we are likely to accept it. Or use `TextSize::of::<&str>` for now. +macro_rules! impl_lentextsize_for_string { + ($($ty:ty),+ $(,)?) => {$( + impl LenTextSize for $ty { + #[inline] + fn len_text_size(self) -> TextSize { + <&str>::len_text_size(self) + } + } + )+}; +} + +impl_lentextsize_for_string! { + &Box, + &'_ String, + &Cow<'_, str>, + &Cow<'_, String>, + &Arc, + &Arc, + &Rc, + &Rc, +} diff --git a/tests/constructors.rs b/tests/constructors.rs index eba587b539..52f1792218 100644 --- a/tests/constructors.rs +++ b/tests/constructors.rs @@ -1,31 +1,34 @@ use { - std::{borrow::Cow, ops::Deref}, + std::{borrow::Cow, sync::Arc}, text_size::*, }; -struct StringLike<'a>(&'a str); +#[derive(Copy, Clone)] +struct BadRope<'a>(&'a [&'a str]); -impl Deref for StringLike<'_> { - type Target = str; - fn deref(&self) -> &Self::Target { - &self.0 +impl LenTextSize for BadRope<'_> { + fn len_text_size(self) -> TextSize { + self.0.iter().copied().map(LenTextSize::len_text_size).sum() } } #[test] fn main() { - let s = ""; - let _ = TextSize::of(&s); + macro_rules! test { + ($($expr:expr),+ $(,)?) => { + $(let _ = TextSize::of($expr);)+ + }; + } - let s = String::new(); - let _ = TextSize::of(&s); - - let s = Cow::Borrowed(""); - let _ = TextSize::of(&s); - - let s = Cow::Owned(String::new()); - let _ = TextSize::of(&s); - - let s = StringLike(""); - let _ = TextSize::of(&s); + test! { + "", + &"", + 'a', + &'a', + &String::new(), + &String::new().into_boxed_str(), + &Arc::new(String::new()), + &Cow::Borrowed(""), + BadRope(&[""]), + } } From b1b7dc101aabf13edfcb97cd756bc659334cd79e Mon Sep 17 00:00:00 2001 From: CAD97 Date: Thu, 26 Mar 2020 17:03:04 -0400 Subject: [PATCH 70/87] Add test for indexing --- tests/indexing.rs | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 tests/indexing.rs diff --git a/tests/indexing.rs b/tests/indexing.rs new file mode 100644 index 0000000000..ebbed7700d --- /dev/null +++ b/tests/indexing.rs @@ -0,0 +1,8 @@ +use text_size::*; + +#[test] +fn main() { + let range = TextRange::default(); + &""[range]; + &String::new()[range]; +} From 8061a90ee0323c36e77e70be93241347414039a8 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Fri, 27 Mar 2020 16:51:58 -0400 Subject: [PATCH 71/87] Imporove test's impl LenTextSize for BadRope --- tests/constructors.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/constructors.rs b/tests/constructors.rs index 52f1792218..1022a4168b 100644 --- a/tests/constructors.rs +++ b/tests/constructors.rs @@ -8,7 +8,7 @@ struct BadRope<'a>(&'a [&'a str]); impl LenTextSize for BadRope<'_> { fn len_text_size(self) -> TextSize { - self.0.iter().copied().map(LenTextSize::len_text_size).sum() + self.0.iter().map(TextSize::of).sum() } } From c94e26f9126978091379ec88830762bc1d10aae8 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Tue, 7 Apr 2020 16:36:32 -0400 Subject: [PATCH 72/87] Rename TextRange::from_len => from_offset --- src/range.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/range.rs b/src/range.rs index 11fe1e702d..a7f47f9d2f 100644 --- a/src/range.rs +++ b/src/range.rs @@ -61,7 +61,7 @@ impl TextRange { TextRange { start, end } } - /// Create a new `TextRange` with the given `start` and `len` (`start..start + len`). + /// Create a new `TextRange` with the given `offset` and `len` (`offset..offset + len`). /// /// # Examples /// @@ -69,16 +69,16 @@ impl TextRange { /// # use text_size::*; /// let text = "0123456789"; /// - /// let start = TextSize::from(2); + /// let offset = TextSize::from(2); /// let length = TextSize::from(5); - /// let range = TextRange::from_len(start, length); + /// let range = TextRange::from_offset(offset, length); /// - /// assert_eq!(range, TextRange::new(start, start + length)); + /// assert_eq!(range, TextRange::new(offset, offset + length)); /// assert_eq!(&text[range], "23456") /// ``` #[inline] - pub fn from_len(start: TextSize, len: TextSize) -> TextRange { - TextRange::new(start, start + len) + pub fn from_offset(offset: TextSize, len: TextSize) -> TextRange { + TextRange::new(offset, offset + len) } /// Create a zero-length range at the specified offset (`offset..offset`). @@ -113,7 +113,7 @@ impl TextRange { /// /// assert_eq!(range.len(), point); /// assert_eq!(range, TextRange::new(TextSize::zero(), point)); - /// assert_eq!(range, TextRange::from_len(TextSize::zero(), point)); + /// assert_eq!(range, TextRange::from_offset(TextSize::zero(), point)); /// ``` #[inline] pub const fn up_to(end: TextSize) -> TextRange { From 951aaa454114d8c1a62af5cc741faafd84bbdf56 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Tue, 7 Apr 2020 16:49:16 -0400 Subject: [PATCH 73/87] Rename TextRange::from_offset => at --- src/range.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/range.rs b/src/range.rs index a7f47f9d2f..c80e5353ed 100644 --- a/src/range.rs +++ b/src/range.rs @@ -71,13 +71,13 @@ impl TextRange { /// /// let offset = TextSize::from(2); /// let length = TextSize::from(5); - /// let range = TextRange::from_offset(offset, length); + /// let range = TextRange::at(offset, length); /// /// assert_eq!(range, TextRange::new(offset, offset + length)); /// assert_eq!(&text[range], "23456") /// ``` #[inline] - pub fn from_offset(offset: TextSize, len: TextSize) -> TextRange { + pub fn at(offset: TextSize, len: TextSize) -> TextRange { TextRange::new(offset, offset + len) } @@ -113,7 +113,7 @@ impl TextRange { /// /// assert_eq!(range.len(), point); /// assert_eq!(range, TextRange::new(TextSize::zero(), point)); - /// assert_eq!(range, TextRange::from_offset(TextSize::zero(), point)); + /// assert_eq!(range, TextRange::at(TextSize::zero(), point)); /// ``` #[inline] pub const fn up_to(end: TextSize) -> TextRange { From 9c3a298268d52ebd3271469e4fe8c101a0b5d014 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Tue, 7 Apr 2020 17:04:54 -0400 Subject: [PATCH 74/87] Rename LenTextSize => TextLen This way, it's a reasonable name to use. --- src/lib.rs | 2 +- src/size.rs | 8 ++++---- src/traits.rs | 36 ++++++++++++++++++------------------ tests/constructors.rs | 4 ++-- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index aea1591003..b39cb186c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,7 @@ mod traits; #[cfg(feature = "serde")] mod serde_impls; -pub use crate::{range::TextRange, size::TextSize, traits::LenTextSize}; +pub use crate::{range::TextRange, size::TextSize, traits::TextLen}; #[cfg(target_pointer_width = "16")] compile_error!("text-size assumes usize >= u32 and does not work on 16-bit targets"); diff --git a/src/size.rs b/src/size.rs index 171f337777..3a2a281329 100644 --- a/src/size.rs +++ b/src/size.rs @@ -1,5 +1,5 @@ use { - crate::LenTextSize, + crate::TextLen, std::{ convert::TryFrom, fmt, iter, @@ -44,7 +44,7 @@ impl TextSize { /// /// Accepts `char`, `&str`, and references to any custom string-like type /// that dereferences to `str`. Types that don't dereference to `str` but - /// want to be usable in this constructor can implement [`LenTextSize`]. + /// want to be usable in this constructor can implement [`TextLen`]. /// /// # Examples /// @@ -57,8 +57,8 @@ impl TextSize { /// assert_eq!(str_size, TextSize::from(13)); /// ``` #[inline] - pub fn of(text: T) -> TextSize { - text.len_text_size() + pub fn of(text: T) -> TextSize { + text.text_len() } /// A size of zero. diff --git a/src/traits.rs b/src/traits.rs index 3944030c62..a19396c133 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -4,52 +4,52 @@ use { }; /// Text-like structures that have a text size. -pub trait LenTextSize: Copy { +pub trait TextLen: Copy { /// The size of this text-alike. - fn len_text_size(self) -> TextSize; + fn text_len(self) -> TextSize; } -impl LenTextSize for &'_ str { +impl TextLen for &'_ str { #[inline] - fn len_text_size(self) -> TextSize { + fn text_len(self) -> TextSize { self.len().try_into().unwrap() } } -impl LenTextSize for char { +impl TextLen for char { #[inline] - fn len_text_size(self) -> TextSize { + fn text_len(self) -> TextSize { (self.len_utf8() as u32).into() } } -impl LenTextSize for &'_ D +impl TextLen for &'_ D where - D: LenTextSize + Copy, + D: TextLen + Copy, { - fn len_text_size(self) -> TextSize { - D::len_text_size(*self) + fn text_len(self) -> TextSize { + D::text_len(*self) } } // Because we could not find a smart blanket impl to do this automatically and // cleanly (rust-analyzer/text-size#36), just provide a bunch of manual impls. -// If a type fits in this macro and you need it to impl LenTextSize, just open -// a PR and we are likely to accept it. Or use `TextSize::of::<&str>` for now. -macro_rules! impl_lentextsize_for_string { +// If a standard type fits in this macro and you need it to impl TextLen, just +// open a PR and we are likely to accept it. Or convince Rust to deref to &str. +macro_rules! impl_textlen_for_string { ($($ty:ty),+ $(,)?) => {$( - impl LenTextSize for $ty { + impl TextLen for $ty { #[inline] - fn len_text_size(self) -> TextSize { - <&str>::len_text_size(self) + fn text_len(self) -> TextSize { + <&str>::text_len(self) } } )+}; } -impl_lentextsize_for_string! { +impl_textlen_for_string! { &Box, - &'_ String, + &String, &Cow<'_, str>, &Cow<'_, String>, &Arc, diff --git a/tests/constructors.rs b/tests/constructors.rs index 1022a4168b..829a28e477 100644 --- a/tests/constructors.rs +++ b/tests/constructors.rs @@ -6,8 +6,8 @@ use { #[derive(Copy, Clone)] struct BadRope<'a>(&'a [&'a str]); -impl LenTextSize for BadRope<'_> { - fn len_text_size(self) -> TextSize { +impl TextLen for BadRope<'_> { + fn text_len(self) -> TextSize { self.0.iter().map(TextSize::of).sum() } } From 53d29fd18a3c754adc5d3e324479f9fa6bb7ec13 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 11 Apr 2020 14:21:53 +0200 Subject: [PATCH 75/87] Add more docs --- CHANGELOG.md | 22 ++++++++++++++++++++++ src/lib.rs | 14 ++++++++++++++ src/range.rs | 14 -------------- src/size.rs | 7 ------- 4 files changed, 36 insertions(+), 21 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..2eb012a6d9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog + +## 1.0.0 :tada: + +* the carate is renmaed to `text-size` from `text_unit` + +Transition table: +- `TextUnit::of_char(c)` ⟹ `TextSize::of(c)` +- `TextUnit::of_str(s)` ⟹ `TextSize::of(s)` +- `TextUnit::from_usize(size)` ⟹ `TextSize::try_from(size).unwrap_or_else(|| panic!(_))` +- `unit.to_usize()` ⟹ `usize::from(size)` +- `TextRange::from_to(from, to)` ⟹ `TextRange::new(from, to)` +- `TextRange::offset_len(offset, size)` ⟹ `TextRange::from_len(offset, size)` +- `range.start()` ⟹ `range.start()` +- `range.end()` ⟹ `range.end()` +- `range.len()` ⟹ `range.len()` +- `range.is_empty()` ⟹ `range.is_empty()` +- `a.is_subrange(b)` ⟹ `b.contains_range(a)` +- `a.intersection(b)` ⟹ `a.intersect(b)` +- `a.extend_to(b)` ⟹ `a.cover(b)` +- `range.contains(offset)` ⟹ `range.contains(point)` +- `range.contains_inclusive(offset)` ⟹ `range.contains_inclusive(point)` diff --git a/src/lib.rs b/src/lib.rs index b39cb186c1..07dc5e80f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,19 @@ //! Newtypes for working with text sizes/ranges in a more type-safe manner. //! +//! This library can help with two things: +//! * Reducing storage requirenments for offsets and ranges, under the +//! assumption that 32 bits is enough. +//! * Providing standard vocabulary types for applications where text ranges +//! are pervasive. +//! +//! However, you should not use this library simply because you work with +//! strings. In the overhelming majority of cases, using `usize` and +//! `std::ops::Range` is better. In particular, if you are publishing a +//! library, using only std types in the interface would make it more +//! interoperable. Similarly, if you are writing something like a lexer, which +//! produces, but does not *store* text ranges, than sticking to `usize` would +//! be better. +//! //! Minimal Supported Rust Version: latest stable. #![forbid(unsafe_code)] diff --git a/src/range.rs b/src/range.rs index c80e5353ed..50fcf82578 100644 --- a/src/range.rs +++ b/src/range.rs @@ -9,20 +9,6 @@ use { /// A range in text, represented as a pair of [`TextSize`][struct@TextSize]. /// /// It is a logic error for `start` to be greater than `end`. -/// -/// # Translation from `text_unit` -/// -/// - `TextRange::from_to(from, to)` ⟹ `TextRange::new(from, to)` -/// - `TextRange::offset_len(offset, size)` ⟹ `TextRange::from_len(offset, size)` -/// - `range.start()` ⟹ `range.start()` -/// - `range.end()` ⟹ `range.end()` -/// - `range.len()` ⟹ `range.len()` -/// - `range.is_empty()` ⟹ `range.is_empty()` -/// - `a.is_subrange(b)` ⟹ `b.contains_range(a)` -/// - `a.intersection(b)` ⟹ `a.intersect(b)` -/// - `a.extend_to(b)` ⟹ `a.cover(b)` -/// - `range.contains(offset)` ⟹ `range.contains(point)` -/// - `range.contains_inclusive(offset)` ⟹ `range.contains_inclusive(point)` #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)] pub struct TextRange { // Invariant: start <= end diff --git a/src/size.rs b/src/size.rs index 3a2a281329..3a0d34b808 100644 --- a/src/size.rs +++ b/src/size.rs @@ -21,13 +21,6 @@ use { /// /// These escape hatches are primarily required for unit testing and when /// converting from UTF-8 size to another coordinate space, such as UTF-16. -/// -/// # Translation from `text_unit` -/// -/// - `TextUnit::of_char(c)` ⟹ `TextSize::of(c)` -/// - `TextUnit::of_str(s)` ⟹ `TextSize::of(s)` -/// - `TextUnit::from_usize(size)` ⟹ `TextSize::try_from(size).unwrap_or_else(|| panic!(_))` -/// - `unit.to_usize()` ⟹ `usize::from(size)` #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct TextSize { pub(crate) raw: u32, From d7ef23ad453337a2e79d44440a22d28efe7bb4d5 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 11 Apr 2020 14:22:16 +0200 Subject: [PATCH 76/87] Ready to launch :rocket:! --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4ccbfdcb7b..9b36860ea9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "text-size" -version = "0.99.0-dev.3" +version = "0.99.0" edition = "2018" authors = [ From a43ca1c55ea549cd052d4b4e1621f7d480210e19 Mon Sep 17 00:00:00 2001 From: Coenen Benjamin Date: Tue, 14 Apr 2020 09:46:19 +0200 Subject: [PATCH 77/87] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 482a055fc3..03a9e8169f 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -# text_unit +# text_size -[![Build Status](https://travis-ci.org/matklad/text_unit.svg?branch=master)](https://travis-ci.org/matklad/text_unit) -[![Crates.io](https://img.shields.io/crates/v/text_unit.svg)](https://crates.io/crates/text_unit) -[![API reference](https://docs.rs/text_unit/badge.svg)](https://docs.rs/text_unit/) +[![Build Status](https://travis-ci.org/matklad/text_size.svg?branch=master)](https://travis-ci.org/matklad/text_size) +[![Crates.io](https://img.shields.io/crates/v/text_size.svg)](https://crates.io/crates/text_size) +[![API reference](https://docs.rs/text_size/badge.svg)](https://docs.rs/text_size/) A library that provides newtype wrappers for `u32` and `(u32, u32)` for use as text offsets. -See the [docs](https://docs.rs/text_unit/) for more. +See the [docs](https://docs.rs/text_size/) for more. ## License From d0896025433ea6a39eab9ef8657eeb05ebb93783 Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Tue, 14 Apr 2020 23:35:02 -0400 Subject: [PATCH 78/87] fix typos --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 07dc5e80f4..92bd36b192 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,17 +1,17 @@ //! Newtypes for working with text sizes/ranges in a more type-safe manner. //! //! This library can help with two things: -//! * Reducing storage requirenments for offsets and ranges, under the +//! * Reducing storage requirements for offsets and ranges, under the //! assumption that 32 bits is enough. //! * Providing standard vocabulary types for applications where text ranges //! are pervasive. //! //! However, you should not use this library simply because you work with -//! strings. In the overhelming majority of cases, using `usize` and +//! strings. In the overwhelming majority of cases, using `usize` and //! `std::ops::Range` is better. In particular, if you are publishing a //! library, using only std types in the interface would make it more //! interoperable. Similarly, if you are writing something like a lexer, which -//! produces, but does not *store* text ranges, than sticking to `usize` would +//! produces, but does not *store* text ranges, then sticking to `usize` would //! be better. //! //! Minimal Supported Rust Version: latest stable. From 4c8a10e8fc2e15a4f550fa4597874aceb02b455b Mon Sep 17 00:00:00 2001 From: Coenen Benjamin Date: Wed, 15 Apr 2020 10:54:27 +0200 Subject: [PATCH 79/87] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 03a9e8169f..365b6028a3 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -# text_size +# text-size -[![Build Status](https://travis-ci.org/matklad/text_size.svg?branch=master)](https://travis-ci.org/matklad/text_size) -[![Crates.io](https://img.shields.io/crates/v/text_size.svg)](https://crates.io/crates/text_size) -[![API reference](https://docs.rs/text_size/badge.svg)](https://docs.rs/text_size/) +[![Build Status](https://travis-ci.org/matklad/text-size.svg?branch=master)](https://travis-ci.org/matklad/text-size) +[![Crates.io](https://img.shields.io/crates/v/text-size.svg)](https://crates.io/crates/text-size) +[![API reference](https://docs.rs/text-size/badge.svg)](https://docs.rs/text-size/) A library that provides newtype wrappers for `u32` and `(u32, u32)` for use as text offsets. -See the [docs](https://docs.rs/text_size/) for more. +See the [docs](https://docs.rs/text-size/) for more. ## License From 408e2047d782f1b45ecf607f1faec031d3a77641 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Fri, 17 Apr 2020 20:10:36 -0400 Subject: [PATCH 80/87] Scope trait TextLen just for primitives --- Cargo.toml | 2 +- src/traits.rs | 15 ++++++++++++--- tests/constructors.rs | 5 +++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9b36860ea9..9c75a9af04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "text-size" -version = "0.99.0" +version = "1.0.0-pre.1" edition = "2018" authors = [ diff --git a/src/traits.rs b/src/traits.rs index a19396c133..c0adacd92b 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -3,12 +3,18 @@ use { std::{borrow::Cow, convert::TryInto, rc::Rc, sync::Arc}, }; -/// Text-like structures that have a text size. -pub trait TextLen: Copy { - /// The size of this text-alike. +use priv_in_pub::Sealed; +mod priv_in_pub { + pub trait Sealed {} +} + +/// Primitives with a textual length that can be passed to [`TextSize::of`]. +pub trait TextLen: Copy + Sealed { + /// The textual length of this primitive. fn text_len(self) -> TextSize; } +impl Sealed for &'_ str {} impl TextLen for &'_ str { #[inline] fn text_len(self) -> TextSize { @@ -16,6 +22,7 @@ impl TextLen for &'_ str { } } +impl Sealed for char {} impl TextLen for char { #[inline] fn text_len(self) -> TextSize { @@ -23,6 +30,7 @@ impl TextLen for char { } } +impl Sealed for &'_ D where D: TextLen + Copy {} impl TextLen for &'_ D where D: TextLen + Copy, @@ -38,6 +46,7 @@ where // open a PR and we are likely to accept it. Or convince Rust to deref to &str. macro_rules! impl_textlen_for_string { ($($ty:ty),+ $(,)?) => {$( + impl Sealed for $ty {} impl TextLen for $ty { #[inline] fn text_len(self) -> TextSize { diff --git a/tests/constructors.rs b/tests/constructors.rs index 829a28e477..9c9d0801ca 100644 --- a/tests/constructors.rs +++ b/tests/constructors.rs @@ -6,7 +6,7 @@ use { #[derive(Copy, Clone)] struct BadRope<'a>(&'a [&'a str]); -impl TextLen for BadRope<'_> { +impl BadRope<'_> { fn text_len(self) -> TextSize { self.0.iter().map(TextSize::of).sum() } @@ -29,6 +29,7 @@ fn main() { &String::new().into_boxed_str(), &Arc::new(String::new()), &Cow::Borrowed(""), - BadRope(&[""]), } + + let _ = BadRope(&[""]).text_len(); } From ccae7686c1d8869ce94d82b7855eee3d32a2c4dd Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 24 Apr 2020 23:57:02 +0200 Subject: [PATCH 81/87] Drop unused API --- src/range.rs | 10 ++++++---- src/size.rs | 16 +--------------- src/traits.rs | 50 +++++++++----------------------------------------- 3 files changed, 16 insertions(+), 60 deletions(-) diff --git a/src/range.rs b/src/range.rs index 50fcf82578..ffb71d3e02 100644 --- a/src/range.rs +++ b/src/range.rs @@ -80,7 +80,7 @@ impl TextRange { /// assert_eq!(range, TextRange::new(point, point)); /// ``` #[inline] - pub const fn empty(offset: TextSize) -> TextRange { + pub fn empty(offset: TextSize) -> TextRange { TextRange { start: offset, end: offset, @@ -102,9 +102,11 @@ impl TextRange { /// assert_eq!(range, TextRange::at(TextSize::zero(), point)); /// ``` #[inline] - pub const fn up_to(end: TextSize) -> TextRange { - let start = TextSize::zero(); - TextRange { start, end } + pub fn up_to(end: TextSize) -> TextRange { + TextRange { + start: 0.into(), + end, + } } } diff --git a/src/size.rs b/src/size.rs index 3a0d34b808..105e158ca4 100644 --- a/src/size.rs +++ b/src/size.rs @@ -53,25 +53,11 @@ impl TextSize { pub fn of(text: T) -> TextSize { text.text_len() } - - /// A size of zero. - /// - /// This is equivalent to `TextSize::default()` or [`TextSize::MIN`], - /// but is more explicit on intent. - #[inline] - pub const fn zero() -> TextSize { - TextSize { raw: 0 } - } } /// Methods to act like a primitive integer type, where reasonably applicable. // Last updated for parity with Rust 1.42.0. impl TextSize { - /// The smallest representable text size. (`u32::MIN`) - pub const MIN: TextSize = TextSize { raw: u32::MIN }; - /// The largest representable text size. (`u32::MAX`) - pub const MAX: TextSize = TextSize { raw: u32::MAX }; - /// Checked addition. Returns `None` if overflow occurred. #[inline] pub fn checked_add(self, rhs: TextSize) -> Option { @@ -172,6 +158,6 @@ where { #[inline] fn sum>(iter: I) -> TextSize { - iter.fold(TextSize::zero(), Add::add) + iter.fold(0.into(), Add::add) } } diff --git a/src/traits.rs b/src/traits.rs index c0adacd92b..d0bb6c1f66 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,7 +1,4 @@ -use { - crate::TextSize, - std::{borrow::Cow, convert::TryInto, rc::Rc, sync::Arc}, -}; +use {crate::TextSize, std::convert::TryInto}; use priv_in_pub::Sealed; mod priv_in_pub { @@ -22,6 +19,14 @@ impl TextLen for &'_ str { } } +impl Sealed for &'_ String {} +impl TextLen for &'_ String { + #[inline] + fn text_len(self) -> TextSize { + self.as_str().text_len() + } +} + impl Sealed for char {} impl TextLen for char { #[inline] @@ -29,40 +34,3 @@ impl TextLen for char { (self.len_utf8() as u32).into() } } - -impl Sealed for &'_ D where D: TextLen + Copy {} -impl TextLen for &'_ D -where - D: TextLen + Copy, -{ - fn text_len(self) -> TextSize { - D::text_len(*self) - } -} - -// Because we could not find a smart blanket impl to do this automatically and -// cleanly (rust-analyzer/text-size#36), just provide a bunch of manual impls. -// If a standard type fits in this macro and you need it to impl TextLen, just -// open a PR and we are likely to accept it. Or convince Rust to deref to &str. -macro_rules! impl_textlen_for_string { - ($($ty:ty),+ $(,)?) => {$( - impl Sealed for $ty {} - impl TextLen for $ty { - #[inline] - fn text_len(self) -> TextSize { - <&str>::text_len(self) - } - } - )+}; -} - -impl_textlen_for_string! { - &Box, - &String, - &Cow<'_, str>, - &Cow<'_, String>, - &Arc, - &Arc, - &Rc, - &Rc, -} From 806f884b777f8bcdf848e7fe70edae5e42abc103 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 25 Apr 2020 11:32:14 +0200 Subject: [PATCH 82/87] Sprinkle inline --- src/range.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/range.rs b/src/range.rs index ffb71d3e02..3b946ab436 100644 --- a/src/range.rs +++ b/src/range.rs @@ -157,6 +157,7 @@ impl TextRange { /// assert!(range.contains(start)); /// assert!(!range.contains(end)); /// ``` + #[inline] pub fn contains(self, offset: TextSize) -> bool { self.start() <= offset && offset < self.end() } @@ -175,6 +176,7 @@ impl TextRange { /// assert!(range.contains_inclusive(start)); /// assert!(range.contains_inclusive(end)); /// ``` + #[inline] pub fn contains_inclusive(self, offset: TextSize) -> bool { self.start() <= offset && offset <= self.end() } @@ -194,6 +196,7 @@ impl TextRange { /// assert!(larger.contains_range(larger)); /// assert!(smaller.contains_range(smaller)); /// ``` + #[inline] pub fn contains_range(self, other: TextRange) -> bool { self.start() <= other.start() && other.end() <= self.end() } @@ -213,6 +216,7 @@ impl TextRange { /// Some(TextRange::new(5.into(), 10.into())), /// ); /// ``` + #[inline] pub fn intersect(self, other: TextRange) -> Option { let start = cmp::max(self.start(), other.start()); let end = cmp::min(self.end(), other.end()); @@ -236,6 +240,7 @@ impl TextRange { /// TextRange::new(0.into(), 20.into()), /// ); /// ``` + #[inline] pub fn cover(self, other: TextRange) -> TextRange { let start = cmp::min(self.start(), other.start()); let end = cmp::max(self.end(), other.end()); @@ -253,6 +258,7 @@ impl TextRange { /// TextRange::new(0.into(), 20.into()), /// ) /// ``` + #[inline] pub fn cover_offset(self, offset: TextSize) -> TextRange { self.cover(TextRange::empty(offset)) } From 253f122f16e6e5c4400c22bf9531bc36093746fa Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 25 Apr 2020 11:38:10 +0200 Subject: [PATCH 83/87] Fix docs --- src/range.rs | 6 +++--- tests/constructors.rs | 29 +++++++++-------------------- tests/main.rs | 2 +- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/range.rs b/src/range.rs index 3b946ab436..fcf286d62e 100644 --- a/src/range.rs +++ b/src/range.rs @@ -98,8 +98,8 @@ impl TextRange { /// let range = TextRange::up_to(point); /// /// assert_eq!(range.len(), point); - /// assert_eq!(range, TextRange::new(TextSize::zero(), point)); - /// assert_eq!(range, TextRange::at(TextSize::zero(), point)); + /// assert_eq!(range, TextRange::new(0.into(), point)); + /// assert_eq!(range, TextRange::at(0.into(), point)); /// ``` #[inline] pub fn up_to(end: TextSize) -> TextRange { @@ -254,7 +254,7 @@ impl TextRange { /// ```rust /// # use text_size::*; /// assert_eq!( - /// TextRange::empty(TextSize::zero()).cover_offset(20.into()), + /// TextRange::empty(0.into()).cover_offset(20.into()), /// TextRange::new(0.into(), 20.into()), /// ) /// ``` diff --git a/tests/constructors.rs b/tests/constructors.rs index 9c9d0801ca..9ff4e19c62 100644 --- a/tests/constructors.rs +++ b/tests/constructors.rs @@ -1,35 +1,24 @@ -use { - std::{borrow::Cow, sync::Arc}, - text_size::*, -}; +use text_size::TextSize; #[derive(Copy, Clone)] struct BadRope<'a>(&'a [&'a str]); impl BadRope<'_> { fn text_len(self) -> TextSize { - self.0.iter().map(TextSize::of).sum() + self.0.iter().copied().map(TextSize::of).sum() } } #[test] fn main() { - macro_rules! test { - ($($expr:expr),+ $(,)?) => { - $(let _ = TextSize::of($expr);)+ - }; - } + let x: char = 'c'; + let _ = TextSize::of(x); - test! { - "", - &"", - 'a', - &'a', - &String::new(), - &String::new().into_boxed_str(), - &Arc::new(String::new()), - &Cow::Borrowed(""), - } + let x: &str = "hello"; + let _ = TextSize::of(x); + + let x: &String = &"hello".into(); + let _ = TextSize::of(x); let _ = BadRope(&[""]).text_len(); } diff --git a/tests/main.rs b/tests/main.rs index f8eb6d6735..5e6b86d659 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -26,7 +26,7 @@ fn checked_math() { assert_eq!(size(1).checked_add(size(1)), Some(size(2))); assert_eq!(size(1).checked_sub(size(1)), Some(size(0))); assert_eq!(size(1).checked_sub(size(2)), None); - assert_eq!(TextSize::MAX.checked_add(size(1)), None); + assert_eq!(size(!0).checked_add(size(1)), None); } #[test] From 5c24bbb969b5058626eba1742653630c5d02f39e Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 25 Apr 2020 11:46:20 +0200 Subject: [PATCH 84/87] :tada: 1.0.0 :tada: --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9c75a9af04..010e3bb4c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "text-size" -version = "1.0.0-pre.1" +version = "1.0.0" edition = "2018" authors = [ From 4bf6b19e182b7cb344f14dbda739527959910172 Mon Sep 17 00:00:00 2001 From: CAD97 Date: Fri, 1 May 2020 13:09:52 -0400 Subject: [PATCH 85/87] Fix TextSize::of docs to no longer suggest implementing TextLen --- src/size.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/size.rs b/src/size.rs index 105e158ca4..ab2ec9a730 100644 --- a/src/size.rs +++ b/src/size.rs @@ -33,11 +33,9 @@ impl fmt::Debug for TextSize { } impl TextSize { - /// The text size of some text-like object. + /// The text size of some primitive text-like object. /// - /// Accepts `char`, `&str`, and references to any custom string-like type - /// that dereferences to `str`. Types that don't dereference to `str` but - /// want to be usable in this constructor can implement [`TextLen`]. + /// Accepts `char`, `&str`, and `&String`. /// /// # Examples /// From e4d0f2b35cab6779eb0dd28297bf53cb13c8c944 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 13 Jan 2021 20:08:43 +0300 Subject: [PATCH 86/87] Allow binary-searching an array of disjoint ranges --- CHANGELOG.md | 6 +++++- Cargo.toml | 2 +- src/range.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eb012a6d9..0167599e55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,12 @@ # Changelog +## 1.1.0 + +* add `TextRange::ordering` method + ## 1.0.0 :tada: -* the carate is renmaed to `text-size` from `text_unit` +* the carate is renamed to `text-size` from `text_unit` Transition table: - `TextUnit::of_char(c)` ⟹ `TextSize::of(c)` diff --git a/Cargo.toml b/Cargo.toml index 010e3bb4c6..19c5a92670 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "text-size" -version = "1.0.0" +version = "1.1.0" edition = "2018" authors = [ diff --git a/src/range.rs b/src/range.rs index fcf286d62e..4a98deec56 100644 --- a/src/range.rs +++ b/src/range.rs @@ -1,3 +1,5 @@ +use cmp::Ordering; + use { crate::TextSize, std::{ @@ -294,6 +296,50 @@ impl TextRange { end: self.end.checked_sub(offset)?, }) } + + /// Relative order of the two ranges (overlapping ranges are considered + /// equal). + /// + /// + /// This is useful when, for example, binary searching an array of disjoint + /// ranges. + /// + /// # Examples + /// + /// ``` + /// # use text_size::*; + /// # use std::cmp::Ordering; + /// + /// let a = TextRange::new(0.into(), 3.into()); + /// let b = TextRange::new(4.into(), 5.into()); + /// assert_eq!(a.ordering(b), Ordering::Less); + /// + /// let a = TextRange::new(0.into(), 3.into()); + /// let b = TextRange::new(3.into(), 5.into()); + /// assert_eq!(a.ordering(b), Ordering::Less); + /// + /// let a = TextRange::new(0.into(), 3.into()); + /// let b = TextRange::new(2.into(), 5.into()); + /// assert_eq!(a.ordering(b), Ordering::Equal); + /// + /// let a = TextRange::new(0.into(), 3.into()); + /// let b = TextRange::new(2.into(), 2.into()); + /// assert_eq!(a.ordering(b), Ordering::Equal); + /// + /// let a = TextRange::new(2.into(), 3.into()); + /// let b = TextRange::new(2.into(), 2.into()); + /// assert_eq!(a.ordering(b), Ordering::Greater); + /// ``` + #[inline] + pub fn ordering(self, other: TextRange) -> Ordering { + if self.end() <= other.start() { + Ordering::Less + } else if other.end() <= self.start() { + Ordering::Greater + } else { + Ordering::Equal + } + } } impl Index for str { From 5f22fbe12550a2c70caff9a8c5347d6722370469 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 30 Jun 2023 09:18:45 +0200 Subject: [PATCH 87/87] Constify some constructors --- Cargo.toml | 2 +- src/range.rs | 14 +++++++------- src/size.rs | 20 ++++++++++++++++---- tests/indexing.rs | 4 ++-- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 19c5a92670..7882f7cc35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "text-size" -version = "1.1.0" +version = "1.1.1" edition = "2018" authors = [ diff --git a/src/range.rs b/src/range.rs index 4a98deec56..9b981642d1 100644 --- a/src/range.rs +++ b/src/range.rs @@ -44,8 +44,8 @@ impl TextRange { /// assert_eq!(range.len(), end - start); /// ``` #[inline] - pub fn new(start: TextSize, end: TextSize) -> TextRange { - assert!(start <= end); + pub const fn new(start: TextSize, end: TextSize) -> TextRange { + assert!(start.raw <= end.raw); TextRange { start, end } } @@ -65,8 +65,8 @@ impl TextRange { /// assert_eq!(&text[range], "23456") /// ``` #[inline] - pub fn at(offset: TextSize, len: TextSize) -> TextRange { - TextRange::new(offset, offset + len) + pub const fn at(offset: TextSize, len: TextSize) -> TextRange { + TextRange::new(offset, TextSize::new(offset.raw + len.raw)) } /// Create a zero-length range at the specified offset (`offset..offset`). @@ -82,7 +82,7 @@ impl TextRange { /// assert_eq!(range, TextRange::new(point, point)); /// ``` #[inline] - pub fn empty(offset: TextSize) -> TextRange { + pub const fn empty(offset: TextSize) -> TextRange { TextRange { start: offset, end: offset, @@ -104,9 +104,9 @@ impl TextRange { /// assert_eq!(range, TextRange::at(0.into(), point)); /// ``` #[inline] - pub fn up_to(end: TextSize) -> TextRange { + pub const fn up_to(end: TextSize) -> TextRange { TextRange { - start: 0.into(), + start: TextSize::new(0), end, } } diff --git a/src/size.rs b/src/size.rs index ab2ec9a730..c950d2edd0 100644 --- a/src/size.rs +++ b/src/size.rs @@ -33,6 +33,12 @@ impl fmt::Debug for TextSize { } impl TextSize { + /// Creates a new instance of `TextSize` from a raw `u32`. + #[inline] + pub const fn new(raw: u32) -> TextSize { + TextSize { raw } + } + /// The text size of some primitive text-like object. /// /// Accepts `char`, `&str`, and `&String`. @@ -58,14 +64,20 @@ impl TextSize { impl TextSize { /// Checked addition. Returns `None` if overflow occurred. #[inline] - pub fn checked_add(self, rhs: TextSize) -> Option { - self.raw.checked_add(rhs.raw).map(|raw| TextSize { raw }) + pub const fn checked_add(self, rhs: TextSize) -> Option { + match self.raw.checked_add(rhs.raw) { + Some(raw) => Some(TextSize { raw }), + None => None, + } } /// Checked subtraction. Returns `None` if overflow occurred. #[inline] - pub fn checked_sub(self, rhs: TextSize) -> Option { - self.raw.checked_sub(rhs.raw).map(|raw| TextSize { raw }) + pub const fn checked_sub(self, rhs: TextSize) -> Option { + match self.raw.checked_sub(rhs.raw) { + Some(raw) => Some(TextSize { raw }), + None => None, + } } } diff --git a/tests/indexing.rs b/tests/indexing.rs index ebbed7700d..93ba3c7cab 100644 --- a/tests/indexing.rs +++ b/tests/indexing.rs @@ -3,6 +3,6 @@ use text_size::*; #[test] fn main() { let range = TextRange::default(); - &""[range]; - &String::new()[range]; + _ = &""[range]; + _ = &String::new()[range]; }