summary refs log tree commit diff stats
path: root/src/suggestion.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/suggestion.rs')
-rw-r--r--src/suggestion.rs149
1 files changed, 128 insertions, 21 deletions
diff --git a/src/suggestion.rs b/src/suggestion.rs
index bad9793..128d025 100644
--- a/src/suggestion.rs
+++ b/src/suggestion.rs
@@ -2,8 +2,9 @@
 // Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT license.
 
-//! A Suggestion.
+//! Suggestion machinery.
 
+//use ::std::collections::HashSet;
 use ::std::ops::Range;
 
 /// A suggested editing operation.
@@ -11,12 +12,12 @@ use ::std::ops::Range;
 /// # Examples
 ///
 /// ```rust
-/// use iosonism::suggestion::Suggestion;
+/// use ::iosonism::suggestion::Suggestion;
 ///
 /// let s = Suggestion::new(3..3, "home".into());
 /// assert_eq!(s.apply("Go ".into()), "Go home");
 /// ```
-#[derive(Debug, PartialEq, Eq, Hash)]
+#[derive(Debug, PartialEq, Eq, Hash, Clone)]
 pub struct Suggestion {
     range: Range<usize>,
     text: String,
@@ -56,26 +57,132 @@ impl Suggestion {
         input
     }
 
-    /// Creates a new `Suggestion` by applying this `Suggestion` on the
-    /// `range` part of the given `input`.
+    /// Expands this suggestion to cover the whole given range.
+    ///
+    /// The text needed to cover the range will be taken from the given input.
     ///
     /// # Panics
     ///
-    /// May panic if this suggestion's range is outside the bounds given by
-    /// `range`. Panics if the given `range` is outside `input`'s bounds, or any
-    /// range is not on an UTF-8 boundary.
-    // FIXME: This function could really use a better description.
-    pub fn expand(&self, mut input: String, range: Range<usize>) -> Self {
-        // It is our understanding that both ranges must be within input.
-        // It's also our understanding that self.range must be within the passed
-        // range.
-        // So we just do our best to enforce those.
-        input.truncate(range.end);
-        input.drain(..range.start);
-        input.replace_range(
-            (self.range.start-range.start)..(self.range.end-range.start),
-            &self.text,
-        );
-        Self::new(range, input)
+    /// Panics if any range is not on an UTF-8 boundary. Panics if the given
+    /// range doesn't cover this suggestion's range, or is outside the input's
+    /// bounds.
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```rust
+    /// use ::iosonism::suggestion::Suggestion;
+    ///
+    /// let mut s = Suggestion::new(7..7, "orld!".into());
+    /// s.expand("hello w", 6..7);
+    /// assert_eq!(s, Suggestion::new(6..7, "world!".into()));
+    /// ```
+    pub fn expand(&mut self, input: &str, range: Range<usize>) {
+        let input = &input[range.clone()];
+        self.text.insert_str(0, &input[..(self.range.start-range.start)]);
+        self.text.push_str(&input[(self.range.end-range.start)..]);
+        self.range = range;
+    }
+}
+
+/// Editing suggestions.
+#[derive(Debug, PartialEq, Eq, Hash, Clone)]
+pub struct Suggestions {
+    range: Range<usize>,
+    suggestions: Vec<Suggestion>,
+}
+
+/// [private] An empty `Suggestions`.
+const EMPTY: Suggestions = Suggestions { range: 0..0, suggestions: Vec::new() };
+
+impl Suggestions {
+    /// Returns the range these suggestions apply to.
+    pub fn get_range(&self) -> Range<usize> {
+        self.range.clone()
+    }
+
+    /// Returns the suggestions.
+    pub fn get_list(&self) -> &[Suggestion] {
+        &self.suggestions
+    }
+
+    /// Returns the suggestions.
+    pub fn take_list(self) -> Vec<Suggestion> {
+        self.suggestions
+    }
+
+    /// Returns `true` if there are no suggestions.
+    pub fn is_empty(&self) -> bool {
+        self.suggestions.is_empty()
+    }
+
+    /// Merges together multiple `Suggestions` for the command.
+    pub fn merge<I: IntoIterator<Item=Suggestions>>(
+        command: &str,
+        input: I,
+    ) -> Self {
+        // check if empty or single-element
+        let mut it = input.into_iter();
+        let first = it.next();
+        if first.is_none() {
+            return EMPTY
+        }
+        let first = first.unwrap();
+        let second = it.next();
+        if second.is_none() {
+            return first
+        }
+        let second = second.unwrap();
+
+        // combine everything back together and hope for the best
+        Self::create(command, first.take_list().into_iter().chain({
+            second.take_list()
+        }).chain(it.flat_map(|e| e.take_list())))
+    }
+
+    /// Creates a `Suggestions` from the individual suggestions for the command.
+    pub fn create<I: IntoIterator<Item=Suggestion>>(
+        command: &str,
+        suggestions: I,
+    ) -> Self {
+        let mut suggestions: Vec<_> = suggestions.into_iter().collect();
+        if suggestions.is_empty() {
+            return EMPTY
+        }
+
+        // find the largest range that spans all the suggestions.
+        let mut start = usize::MAX;
+        let mut end = 0;
+        for suggestion in suggestions.iter() {
+            start = ::std::cmp::min(suggestion.get_range().start, start);
+            end = ::std::cmp::max(suggestion.get_range().end, end);
+        }
+        let range = start..end;
+
+        // expand all suggestions.
+        for suggestion in suggestions.iter_mut() {
+            // cloning a range is basically free.
+            suggestion.expand(command, range.clone());
+        }
+
+        // brigadier uses a HashSet first for deduplication. we currently use
+        // a case-sensitive sort and then do the deduplication. we can probably
+        // do something better here, and maybe even be language aware?
+        // FIXME investigate
+        // note that by the time we're here, all suggestions have been expanded,
+        // so the ranges are all the same (and no longer relevant).
+        suggestions.sort_unstable_by(|a, b| {
+            ::std::cmp::Ord::cmp(a.get_text(), b.get_text())
+        });
+        suggestions.dedup();
+
+        Self {
+            range: range,
+            suggestions: suggestions,
+        }
     }
 }
+
+///// Helper for building suggestions.
+//pub struct SuggestionsBuilder<'a>(&'a str);