1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
pub mod favicon_cache;
pub mod feed_parser;
pub mod html2text;
pub mod mercury_parser;
pub mod opml;

// Two goals here:
//   1) append an * after every term so it becomes a prefix search
//      (see <https://www.sqlite.org/fts3.html#section_3>), and
//   2) strip out common words/operators that might get interpreted as
//      search operators.
// We ignore everything inside quotes to give the user a way to
// override our algorithm here.  The idea is to offer one search query
// syntax for Geary that we can use locally and via IMAP, etc.
pub fn prepare_search_term(search_term: &str) -> String {
    let mut search_term_balanced = search_term.replace("'", " ");

    // Remove the last quote if it's not balanced.  This has the
    // benefit of showing decent results as you type a quoted phrase.
    if count_char(&search_term, &'"') % 2 != 0 {
        if let Some(last_quote) = search_term.rfind('"') {
            search_term_balanced.replace_range(last_quote..last_quote + 1, " ");
        }
    }

    let mut in_quote = false;
    let mut prepared_search_term = String::new();
    for word in search_term_balanced.split_whitespace() {
        let mut quotes = count_char(word, &'"');
        let mut word = word.to_owned();

        if !in_quote && quotes > 0 {
            in_quote = true;
            quotes -= 1;
        }

        if !in_quote {
            let lower = word.to_lowercase();
            if lower == "and" || lower == "or" || lower == "not" || lower == "near" {
                continue;
            }

            if word.starts_with("-") {
                word.remove(0);
            }

            if word == "" {
                continue;
            }

            word = format!("\"{}*\"", word);
        }

        if in_quote && quotes % 2 != 0 {
            in_quote = false;
        }

        prepared_search_term.push_str(&word);
        prepared_search_term.push_str(" ");
    }

    prepared_search_term
}

pub fn count_char(string: &str, character: &char) -> usize {
    string.chars().filter(|c| c == character).count()
}

pub fn vec_to_option<T>(vector: Vec<T>) -> Option<Vec<T>> {
    if vector.is_empty() {
        None
    } else {
        Some(vector)
    }
}

pub fn option_to_bool(option: Option<bool>) -> bool {
    match option {
        Some(value) => value,
        None => false,
    }
}