summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYuvi Panda <yuvipanda@gmail.com>2021-05-09 13:03:56 +0530
committerGitHub <noreply@github.com>2021-05-09 08:33:56 +0100
commit19bd00f62005d07fc22ef72558e1102a7bb13b03 (patch)
tree818aaf786755e37aec2dcfa40ea938f42edc72db
parent07c54610138fb23cc71bb61516539cf67574e19c (diff)
Support fulltext search of commands (#75)
-rw-r--r--atuin-client/src/database.rs26
-rw-r--r--atuin-client/src/settings.rs11
-rw-r--r--src/command/mod.rs1
-rw-r--r--src/command/search.rs26
4 files changed, 51 insertions, 13 deletions
diff --git a/atuin-client/src/database.rs b/atuin-client/src/database.rs
index e56a8df0..160c6054 100644
--- a/atuin-client/src/database.rs
+++ b/atuin-client/src/database.rs
@@ -13,6 +13,7 @@ use sqlx::sqlite::{
use sqlx::Row;
use super::history::History;
+use super::settings::SearchMode;
#[async_trait]
pub trait Database {
@@ -34,7 +35,12 @@ pub trait Database {
async fn last(&self) -> Result<History>;
async fn before(&self, timestamp: chrono::DateTime<Utc>, count: i64) -> Result<Vec<History>>;
- async fn search(&self, limit: Option<i64>, query: &str) -> Result<Vec<History>>;
+ async fn search(
+ &self,
+ limit: Option<i64>,
+ search_mode: SearchMode,
+ query: &str,
+ ) -> Result<Vec<History>>;
async fn query_history(&self, query: &str) -> Result<Vec<History>>;
}
@@ -185,7 +191,7 @@ impl Database for Sqlite {
// inject the unique check
if unique {
"where timestamp = (
- select max(timestamp) from history
+ select max(timestamp) from history
where h.command = history.command
)"
} else {
@@ -268,16 +274,26 @@ impl Database for Sqlite {
Ok(res.0)
}
- async fn search(&self, limit: Option<i64>, query: &str) -> Result<Vec<History>> {
+ async fn search(
+ &self,
+ limit: Option<i64>,
+ search_mode: SearchMode,
+ query: &str,
+ ) -> Result<Vec<History>> {
let query = query.to_string().replace("*", "%"); // allow wildcard char
let limit = limit.map_or("".to_owned(), |l| format!("limit {}", l));
+ let query = match search_mode {
+ SearchMode::Prefix => query,
+ SearchMode::FullText => format!("%{}", query),
+ };
+
let res = sqlx::query(
format!(
"select * from history h
- where command like ?1 || '%'
+ where command like ?1 || '%'
and timestamp = (
- select max(timestamp) from history
+ select max(timestamp) from history
where h.command = history.command
)
order by timestamp desc {}",
diff --git a/atuin-client/src/settings.rs b/atuin-client/src/settings.rs
index 4ea4be84..7ccbaf32 100644
--- a/atuin-client/src/settings.rs
+++ b/atuin-client/src/settings.rs
@@ -10,6 +10,15 @@ use parse_duration::parse;
pub const HISTORY_PAGE_SIZE: i64 = 100;
+#[derive(Clone, Debug, Deserialize, Copy)]
+pub enum SearchMode {
+ #[serde(rename = "prefix")]
+ Prefix,
+
+ #[serde(rename = "fulltext")]
+ FullText,
+}
+
#[derive(Clone, Debug, Deserialize)]
pub struct Settings {
pub dialect: String,
@@ -19,6 +28,7 @@ pub struct Settings {
pub db_path: String,
pub key_path: String,
pub session_path: String,
+ pub search_mode: SearchMode,
// This is automatically loaded when settings is created. Do not set in
// config! Keep secrets and settings apart.
@@ -100,6 +110,7 @@ impl Settings {
s.set_default("auto_sync", true)?;
s.set_default("sync_frequency", "1h")?;
s.set_default("sync_address", "https://api.atuin.sh")?;
+ s.set_default("search_mode", "prefix")?;
if config_file.exists() {
s.merge(ConfigFile::with_name(config_file.to_str().unwrap()))?;
diff --git a/src/command/mod.rs b/src/command/mod.rs
index b16aae4d..8af64cb0 100644
--- a/src/command/mod.rs
+++ b/src/command/mod.rs
@@ -114,6 +114,7 @@ impl AtuinCmd {
query,
} => {
search::run(
+ &client_settings,
cwd,
exit,
interactive,
diff --git a/src/command/search.rs b/src/command/search.rs
index af2a1e43..f49f16e2 100644
--- a/src/command/search.rs
+++ b/src/command/search.rs
@@ -16,6 +16,7 @@ use unicode_width::UnicodeWidthStr;
use atuin_client::database::Database;
use atuin_client::history::History;
+use atuin_client::settings::{SearchMode, Settings};
use crate::command::event::{Event, Events};
@@ -130,10 +131,14 @@ impl State {
}
}
-async fn query_results(app: &mut State, db: &mut (impl Database + Send + Sync)) -> Result<()> {
+async fn query_results(
+ app: &mut State,
+ search_mode: SearchMode,
+ db: &mut (impl Database + Send + Sync),
+) -> Result<()> {
let results = match app.input.as_str() {
"" => db.list(Some(200), true).await?,
- i => db.search(Some(200), i).await?,
+ i => db.search(Some(200), search_mode, i).await?,
};
app.results = results;
@@ -149,6 +154,7 @@ async fn query_results(app: &mut State, db: &mut (impl Database + Send + Sync))
async fn key_handler(
input: Key,
+ search_mode: SearchMode,
db: &mut (impl Database + Send + Sync),
app: &mut State,
) -> Option<String> {
@@ -165,11 +171,11 @@ async fn key_handler(
}
Key::Char(c) => {
app.input.push(c);
- query_results(app, db).await.unwrap();
+ query_results(app, search_mode, db).await.unwrap();
}
Key::Backspace => {
app.input.pop();
- query_results(app, db).await.unwrap();
+ query_results(app, search_mode, db).await.unwrap();
}
Key::Down => {
let i = match app.results_state.selected() {
@@ -277,6 +283,7 @@ fn draw<T: Backend>(f: &mut Frame<'_, T>, history_count: i64, app: &mut State) {
#[allow(clippy::clippy::cast_possible_truncation)]
async fn select_history(
query: &[String],
+ search_mode: SearchMode,
db: &mut (impl Database + Send + Sync),
) -> Result<String> {
let stdout = stdout().into_raw_mode()?;
@@ -294,13 +301,13 @@ async fn select_history(
results_state: ListState::default(),
};
- query_results(&mut app, db).await?;
+ query_results(&mut app, search_mode, db).await?;
loop {
let history_count = db.history_count().await?;
// Handle input
if let Event::Input(input) = events.next()? {
- if let Some(output) = key_handler(input, db, &mut app).await {
+ if let Some(output) = key_handler(input, search_mode, db, &mut app).await {
return Ok(output);
}
}
@@ -313,6 +320,7 @@ async fn select_history(
// it is going to have a lot of args
#[allow(clippy::clippy::clippy::too_many_arguments)]
pub async fn run(
+ settings: &Settings,
cwd: Option<String>,
exit: Option<i64>,
interactive: bool,
@@ -339,10 +347,12 @@ pub async fn run(
};
if interactive {
- let item = select_history(query, db).await?;
+ let item = select_history(query, settings.search_mode, db).await?;
eprintln!("{}", item);
} else {
- let results = db.search(None, query.join(" ").as_str()).await?;
+ let results = db
+ .search(None, settings.search_mode, query.join(" ").as_str())
+ .await?;
// TODO: This filtering would be better done in the SQL query, I just
// need a nice way of building queries.