Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/ui/picker/query.rs')
-rw-r--r--helix-term/src/ui/picker/query.rs373
1 files changed, 0 insertions, 373 deletions
diff --git a/helix-term/src/ui/picker/query.rs b/helix-term/src/ui/picker/query.rs
deleted file mode 100644
index 005ddee4..00000000
--- a/helix-term/src/ui/picker/query.rs
+++ /dev/null
@@ -1,373 +0,0 @@
-use std::{collections::HashMap, mem, ops::Range, sync::Arc};
-
-#[derive(Debug)]
-pub(super) struct PickerQuery {
- /// The column names of the picker.
- column_names: Box<[Arc<str>]>,
- /// The index of the primary column in `column_names`.
- /// The primary column is selected by default unless another
- /// field is specified explicitly with `%fieldname`.
- primary_column: usize,
- /// The mapping between column names and input in the query
- /// for those columns.
- inner: HashMap<Arc<str>, Arc<str>>,
- /// The byte ranges of the input text which are used as input for each column.
- /// This is calculated at parsing time for use in [Self::active_column].
- /// This Vec is naturally sorted in ascending order and ranges do not overlap.
- column_ranges: Vec<(Range<usize>, Option<Arc<str>>)>,
-}
-
-impl PartialEq<HashMap<Arc<str>, Arc<str>>> for PickerQuery {
- fn eq(&self, other: &HashMap<Arc<str>, Arc<str>>) -> bool {
- self.inner.eq(other)
- }
-}
-
-impl PickerQuery {
- pub(super) fn new<I: Iterator<Item = Arc<str>>>(
- column_names: I,
- primary_column: usize,
- ) -> Self {
- let column_names: Box<[_]> = column_names.collect();
- let inner = HashMap::with_capacity(column_names.len());
- let column_ranges = vec![(0..usize::MAX, Some(column_names[primary_column].clone()))];
- Self {
- column_names,
- primary_column,
- inner,
- column_ranges,
- }
- }
-
- pub(super) fn get(&self, column: &str) -> Option<&Arc<str>> {
- self.inner.get(column)
- }
-
- pub(super) fn parse(&mut self, input: &str) -> HashMap<Arc<str>, Arc<str>> {
- let mut fields: HashMap<Arc<str>, String> = HashMap::new();
- let primary_field = &self.column_names[self.primary_column];
- let mut escaped = false;
- let mut in_field = false;
- let mut field = None;
- let mut text = String::new();
- self.column_ranges.clear();
- self.column_ranges
- .push((0..usize::MAX, Some(primary_field.clone())));
-
- macro_rules! finish_field {
- () => {
- let key = field.take().unwrap_or(primary_field);
-
- // Trims one space from the end, enabling leading and trailing
- // spaces in search patterns, while also retaining spaces as separators
- // between column filters.
- let pat = text.strip_suffix(' ').unwrap_or(&text);
-
- if let Some(pattern) = fields.get_mut(key) {
- pattern.push(' ');
- pattern.push_str(pat);
- } else {
- fields.insert(key.clone(), pat.to_string());
- }
- text.clear();
- };
- }
-
- for (idx, ch) in input.char_indices() {
- match ch {
- // Backslash escaping
- _ if escaped => {
- // '%' is the only character that is special cased.
- // You can escape it to prevent parsing the text that
- // follows it as a field name.
- if ch != '%' {
- text.push('\\');
- }
- text.push(ch);
- escaped = false;
- }
- '\\' => escaped = !escaped,
- '%' => {
- if !text.is_empty() {
- finish_field!();
- }
- let (range, _field) = self
- .column_ranges
- .last_mut()
- .expect("column_ranges is non-empty");
- range.end = idx;
- in_field = true;
- }
- ' ' if in_field => {
- text.clear();
- in_field = false;
- }
- _ if in_field => {
- text.push(ch);
- // Go over all columns and their indices, find all that starts with field key,
- // select a column that fits key the most.
- field = self
- .column_names
- .iter()
- .filter(|col| col.starts_with(&text))
- // select "fittest" column
- .min_by_key(|col| col.len());
-
- // Update the column range for this column.
- if let Some((_range, current_field)) = self
- .column_ranges
- .last_mut()
- .filter(|(range, _)| range.end == usize::MAX)
- {
- *current_field = field.cloned();
- } else {
- self.column_ranges.push((idx..usize::MAX, field.cloned()));
- }
- }
- _ => text.push(ch),
- }
- }
-
- if !in_field && !text.is_empty() {
- finish_field!();
- }
-
- let new_inner: HashMap<_, _> = fields
- .into_iter()
- .map(|(field, query)| (field, query.as_str().into()))
- .collect();
-
- mem::replace(&mut self.inner, new_inner)
- }
-
- /// Finds the column which the cursor is 'within' in the last parse.
- ///
- /// The cursor is considered to be within a column when it is placed within any
- /// of a column's text. See the `active_column_test` unit test below for examples.
- ///
- /// `cursor` is a byte index that represents the location of the prompt's cursor.
- pub fn active_column(&self, cursor: usize) -> Option<&Arc<str>> {
- let point = self
- .column_ranges
- .partition_point(|(range, _field)| cursor > range.end);
-
- self.column_ranges
- .get(point)
- .filter(|(range, _field)| cursor >= range.start && cursor <= range.end)
- .and_then(|(_range, field)| field.as_ref())
- }
-}
-
-#[cfg(test)]
-mod test {
- use helix_core::hashmap;
-
- use super::*;
-
- #[test]
- fn parse_query_test() {
- let mut query = PickerQuery::new(
- [
- "primary".into(),
- "field1".into(),
- "field2".into(),
- "another".into(),
- "anode".into(),
- ]
- .into_iter(),
- 0,
- );
-
- // Basic field splitting
- query.parse("hello world");
- assert_eq!(
- query,
- hashmap!(
- "primary".into() => "hello world".into(),
- )
- );
- query.parse("hello %field1 world %field2 !");
- assert_eq!(
- query,
- hashmap!(
- "primary".into() => "hello".into(),
- "field1".into() => "world".into(),
- "field2".into() => "!".into(),
- )
- );
- query.parse("%field1 abc %field2 def xyz");
- assert_eq!(
- query,
- hashmap!(
- "field1".into() => "abc".into(),
- "field2".into() => "def xyz".into(),
- )
- );
-
- // Trailing space is trimmed
- query.parse("hello ");
- assert_eq!(
- query,
- hashmap!(
- "primary".into() => "hello".into(),
- )
- );
-
- // Unknown fields are trimmed.
- query.parse("hello %foo");
- assert_eq!(
- query,
- hashmap!(
- "primary".into() => "hello".into(),
- )
- );
-
- // Multiple words in a field
- query.parse("hello %field1 a b c");
- assert_eq!(
- query,
- hashmap!(
- "primary".into() => "hello".into(),
- "field1".into() => "a b c".into(),
- )
- );
-
- // Escaping
- query.parse(r#"hello\ world"#);
- assert_eq!(
- query,
- hashmap!(
- "primary".into() => r#"hello\ world"#.into(),
- )
- );
- query.parse(r#"hello \%field1 world"#);
- assert_eq!(
- query,
- hashmap!(
- "primary".into() => "hello %field1 world".into(),
- )
- );
- query.parse(r#"%field1 hello\ world"#);
- assert_eq!(
- query,
- hashmap!(
- "field1".into() => r#"hello\ world"#.into(),
- )
- );
- query.parse(r#"hello %field1 a\"b"#);
- assert_eq!(
- query,
- hashmap!(
- "primary".into() => "hello".into(),
- "field1".into() => r#"a\"b"#.into(),
- )
- );
- query.parse(r#"%field1 hello\ world"#);
- assert_eq!(
- query,
- hashmap!(
- "field1".into() => r#"hello\ world"#.into(),
- )
- );
- query.parse(r#"\bfoo\b"#);
- assert_eq!(
- query,
- hashmap!(
- "primary".into() => r#"\bfoo\b"#.into(),
- )
- );
- query.parse(r#"\\n"#);
- assert_eq!(
- query,
- hashmap!(
- "primary".into() => r#"\\n"#.into(),
- )
- );
-
- // Only the prefix of a field is required.
- query.parse("hello %anot abc");
- assert_eq!(
- query,
- hashmap!(
- "primary".into() => "hello".into(),
- "another".into() => "abc".into(),
- )
- );
- // The shortest matching the prefix is selected.
- query.parse("hello %ano abc");
- assert_eq!(
- query,
- hashmap!(
- "primary".into() => "hello".into(),
- "anode".into() => "abc".into()
- )
- );
- // Multiple uses of a column are concatenated with space separators.
- query.parse("hello %field1 xyz %fie abc");
- assert_eq!(
- query,
- hashmap!(
- "primary".into() => "hello".into(),
- "field1".into() => "xyz abc".into()
- )
- );
- query.parse("hello %fie abc");
- assert_eq!(
- query,
- hashmap!(
- "primary".into() => "hello".into(),
- "field1".into() => "abc".into()
- )
- );
- // The primary column can be explicitly qualified.
- query.parse("hello %fie abc %prim world");
- assert_eq!(
- query,
- hashmap!(
- "primary".into() => "hello world".into(),
- "field1".into() => "abc".into()
- )
- );
- }
-
- #[test]
- fn active_column_test() {
- fn active_column<'a>(query: &'a mut PickerQuery, input: &str) -> Option<&'a str> {
- let cursor = input.find('|').expect("cursor must be indicated with '|'");
- let input = input.replace('|', "");
- query.parse(&input);
- query.active_column(cursor).map(AsRef::as_ref)
- }
-
- let mut query = PickerQuery::new(
- ["primary".into(), "foo".into(), "bar".into()].into_iter(),
- 0,
- );
-
- assert_eq!(active_column(&mut query, "|"), Some("primary"));
- assert_eq!(active_column(&mut query, "hello| world"), Some("primary"));
- assert_eq!(active_column(&mut query, "|%foo hello"), Some("primary"));
- assert_eq!(active_column(&mut query, "%foo|"), Some("foo"));
- assert_eq!(active_column(&mut query, "%|"), None);
- assert_eq!(active_column(&mut query, "%baz|"), None);
- assert_eq!(active_column(&mut query, "%quiz%|"), None);
- assert_eq!(active_column(&mut query, "%foo hello| world"), Some("foo"));
- assert_eq!(active_column(&mut query, "%foo hello world|"), Some("foo"));
- assert_eq!(active_column(&mut query, "%foo| hello world"), Some("foo"));
- assert_eq!(active_column(&mut query, "%|foo hello world"), Some("foo"));
- assert_eq!(active_column(&mut query, "%f|oo hello world"), Some("foo"));
- assert_eq!(active_column(&mut query, "hello %f|oo world"), Some("foo"));
- assert_eq!(
- active_column(&mut query, "hello %f|oo world %bar !"),
- Some("foo")
- );
- assert_eq!(
- active_column(&mut query, "hello %foo wo|rld %bar !"),
- Some("foo")
- );
- assert_eq!(
- active_column(&mut query, "hello %foo world %bar !|"),
- Some("bar")
- );
- }
-}