ClickHouse
108 строк · 3.7 Кб
1use cxx::{CxxString, CxxVector};
2use skim::prelude::*;
3use std::panic;
4use term::terminfo::TermInfo;
5
6#[cxx::bridge]
7mod ffi {
8extern "Rust" {
9fn skim(prefix: &CxxString, words: &CxxVector<CxxString>) -> Result<String>;
10}
11}
12
13struct Item {
14text_no_newlines: String,
15orig_text: String,
16}
17impl Item {
18fn new(text: String) -> Self {
19Self {
20// Text that will be printed by skim, and will be used for matching.
21//
22// Text that will be shown should not contains new lines since in this case skim may
23// live some symbols on the screen, and this looks odd.
24text_no_newlines: text.replace("\n", " "),
25// This will be used when the match had been selected.
26orig_text: text,
27}
28}
29}
30impl SkimItem for Item {
31fn text(&self) -> Cow<str> {
32Cow::Borrowed(&self.text_no_newlines)
33}
34
35fn output(&self) -> Cow<str> {
36Cow::Borrowed(&self.orig_text)
37}
38}
39
40fn skim_impl(prefix: &CxxString, words: &CxxVector<CxxString>) -> Result<String, String> {
41// Let's check is terminal available. To avoid panic.
42if let Err(err) = TermInfo::from_env() {
43return Err(format!("{}", err));
44}
45
46let options = SkimOptionsBuilder::default()
47.height(Some("30%"))
48.query(Some(prefix.to_str().unwrap()))
49.tac(true)
50.tiebreak(Some("-score".to_string()))
51// Exact mode performs better for SQL.
52//
53// Default fuzzy search is too smart for SQL, it even takes into account the case, which
54// should not be accounted (you don't want to type "SELECT" instead of "select" to find the
55// query).
56//
57// Exact matching seems better algorithm for SQL, it is not 100% exact, it splits by space,
58// and apply separate matcher actually for each word.
59// Note, that if you think that "space is not enough" as the delimiter, then you should
60// first know that this is the delimiter only for the input query, so to match
61// "system.query_log" you can use "sy qu log"
62// Also it should be more common for users who did not know how to use fuzzy search.
63// (also you can disable exact mode by prepending "'" char).
64//
65// Also it ignores the case correctly, i.e. it does not have penalty for case mismatch,
66// like fuzzy algorithms (take a look at SkimScoreConfig::penalty_case_mismatch).
67.exact(true)
68.case(CaseMatching::Ignore)
69.build()
70.unwrap();
71
72let (tx, rx): (SkimItemSender, SkimItemReceiver) = unbounded();
73for word in words {
74tx.send(Arc::new(Item::new(word.to_string()))).unwrap();
75}
76// so that skim could know when to stop waiting for more items.
77drop(tx);
78
79let output = Skim::run_with(&options, Some(rx));
80if output.is_none() {
81return Err("skim return nothing".to_string());
82}
83let output = output.unwrap();
84if output.is_abort {
85return Ok("".to_string());
86}
87
88if output.selected_items.is_empty() {
89return Err("No items had been selected".to_string());
90}
91Ok(output.selected_items[0].output().to_string())
92}
93
94fn skim(prefix: &CxxString, words: &CxxVector<CxxString>) -> Result<String, String> {
95match panic::catch_unwind(|| skim_impl(prefix, words)) {
96Err(err) => {
97let e = if let Some(s) = err.downcast_ref::<String>() {
98format!("{}", s)
99} else if let Some(s) = err.downcast_ref::<&str>() {
100format!("{}", s)
101} else {
102format!("Unknown panic type: {:?}", err.type_id())
103};
104Err(format!("Rust panic: {:?}", e))
105}
106Ok(res) => res,
107}
108}
109