Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 35 additions & 70 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ chrono = { version = "0.4.39", features = ["serde"] }
clap = { version = "4.5.45", features = ["derive"] }
colored = "3.0.0"
console = "0.16.0"
inquire = "0.7.5"
dialoguer = { version = "0.12.0", features = ["fuzzy-select"] }
convert_case = "0.8.0"
derive_more = { version = "2.0.1", features = ["from", "display", "debug"] }
derive_setters = "0.1.6"
Expand Down Expand Up @@ -138,3 +138,4 @@ forge_tool_macros = { path = "crates/forge_tool_macros" }
forge_tracker = { path = "crates/forge_tracker" }
forge_walker = { path = "crates/forge_walker" }
forge_json_repair = { path = "crates/forge_json_repair" }
forge_select = { path = "crates/forge_select" }
2 changes: 1 addition & 1 deletion crates/forge_infra/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ reqwest.workspace = true

bytes.workspace = true
pretty_assertions.workspace = true
inquire.workspace = true
forge_select.workspace = true
tempfile.workspace = true
rmcp.workspace = true
tracing.workspace = true
Expand Down
55 changes: 17 additions & 38 deletions crates/forge_infra/src/inquire.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use anyhow::Result;
use forge_select::ForgeSelect;
use forge_services::UserInfra;
use inquire::ui::{RenderConfig, Styled};
use inquire::{InquireError, MultiSelect, Select, Text};

pub struct ForgeInquire;

Expand All @@ -16,68 +15,48 @@ impl ForgeInquire {
Self
}

fn render_config() -> RenderConfig<'static> {
RenderConfig::default()
.with_scroll_up_prefix(Styled::new("⇡"))
.with_scroll_down_prefix(Styled::new("⇣"))
.with_highlighted_option_prefix(Styled::new("➤"))
}

async fn prompt<T, F>(&self, f: F) -> Result<Option<T>>
where
F: FnOnce() -> std::result::Result<T, InquireError> + Send + 'static,
F: FnOnce() -> Result<Option<T>> + Send + 'static,
T: Send + 'static,
{
let result = tokio::task::spawn_blocking(f).await?;

match result {
Ok(value) => Ok(Some(value)),
Err(InquireError::OperationCanceled | InquireError::OperationInterrupted) => Ok(None),
Err(e) => Err(e.into()),
}
tokio::task::spawn_blocking(f).await?
}
}

#[async_trait::async_trait]
impl UserInfra for ForgeInquire {
async fn prompt_question(&self, question: &str) -> Result<Option<String>> {
let question = question.to_string();
self.prompt(move || {
Text::new(&question)
.with_render_config(Self::render_config())
.with_help_message("Press Enter to submit, ESC to cancel")
.prompt()
})
.await
self.prompt(move || ForgeSelect::input(&question).allow_empty(true).prompt())
.await
}

async fn select_one<T: std::fmt::Display + Send + 'static>(
&self,
message: &str,
options: Vec<T>,
) -> Result<Option<T>> {
if options.is_empty() {
return Ok(None);
}

let message = message.to_string();
self.prompt(move || {
Select::new(&message, options)
.with_render_config(Self::render_config())
.with_help_message("Use arrow keys to navigate, Enter to select, ESC to cancel")
.prompt()
})
.await
self.prompt(move || ForgeSelect::select_owned(&message, options).prompt())
.await
}

async fn select_many<T: std::fmt::Display + Clone + Send + 'static>(
&self,
message: &str,
options: Vec<T>,
) -> Result<Option<Vec<T>>> {
if options.is_empty() {
return Ok(None);
}

let message = message.to_string();
self.prompt(move || {
MultiSelect::new(&message, options)
.with_render_config(Self::render_config())
.with_help_message("Use arrow keys to navigate, Space to select/deselect, Enter to confirm, ESC to cancel")
.prompt()
})
.await
self.prompt(move || ForgeSelect::multi_select(&message, options).prompt())
.await
}
}
2 changes: 1 addition & 1 deletion crates/forge_main/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ forge_display.workspace = true
forge_tracker.workspace = true

forge_spinner.workspace = true
inquire.workspace = true
forge_select.workspace = true

merge.workspace = true
forge_fs.workspace = true
Expand Down
22 changes: 14 additions & 8 deletions crates/forge_main/src/conversation_selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ use anyhow::Result;
use chrono::Utc;
use colored::Colorize;
use forge_api::Conversation;

use crate::select::ForgeSelect;
use forge_select::ForgeSelect;

/// Logic for selecting conversations from a list
pub struct ConversationSelector;
Expand All @@ -14,7 +13,9 @@ impl ConversationSelector {
/// Select a conversation from the provided list
///
/// Returns the selected conversation ID, or None if no selection was made
pub fn select_conversation(conversations: &[Conversation]) -> Result<Option<Conversation>> {
pub async fn select_conversation(
conversations: &[Conversation],
) -> Result<Option<Conversation>> {
if conversations.is_empty() {
return Ok(None);
}
Expand Down Expand Up @@ -53,6 +54,7 @@ impl ConversationSelector {

let max_title_length: usize = titles.clone().map(|s| s.len()).max().unwrap_or(0);

#[derive(Clone)]
struct ConversationItem((String, Conversation));
impl Display for ConversationItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Expand All @@ -67,10 +69,12 @@ impl ConversationSelector {
.map(ConversationItem)
.collect::<Vec<_>>();

if let Some(selected) =
if let Some(selected) = tokio::task::spawn_blocking(|| {
ForgeSelect::select("Select the conversation to resume:", conversations)
.with_help_message("Type a name or use arrow keys to navigate and Enter to select")
.prompt()?
.prompt()
})
.await??
{
Ok(Some(selected.0.1))
} else {
Expand Down Expand Up @@ -99,10 +103,12 @@ mod tests {
}
}

#[test]
fn test_select_conversation_empty_list() {
#[tokio::test]
async fn test_select_conversation_empty_list() {
let conversations = vec![];
let result = ConversationSelector::select_conversation(&conversations).unwrap();
let result = ConversationSelector::select_conversation(&conversations)
.await
.unwrap();
assert!(result.is_none());
}

Expand Down
Loading
Loading