diff --git a/src/commands/debug.rs b/src/commands/debug.rs index c05bf97..0cb5437 100644 --- a/src/commands/debug.rs +++ b/src/commands/debug.rs @@ -1,4 +1,6 @@ use anyhow::{bail, Context as _}; +use poise::AutocompleteChoice; +use tracing::{debug, instrument}; use super::super::{drql, Context}; @@ -34,11 +36,52 @@ async fn scan( Ok(()) } +#[instrument(skip_all, fields(query = query))] +async fn parse_one_autocomplete(_ctx: Context<'_>, query: &str) -> Vec> { + // When we encounter an error, we want to split it across multiple lines because Discord only + // gives us 100 characters for the 'name' field in AutocompleteChoice. We split as follows: + // 1. We always split on newlines within the error message. + // 2. For a single line of the error message, we split on whitespace. + + // TODO: Move this to a function? It's sorta repetitive for both autocomplete functions + if let Err(e) = drql::parser::parse_drql(query) { + debug!("Returning parse error response to autocomplete: {e:#}"); + format!("Encountered an error while parsing:\n{e:#}") + .split('\n') + .flat_map(|part| { + crate::util::wrap_string_vec( + &part + .split_whitespace() + .map(std::string::ToString::to_string) + .collect::>(), + " ", + 100, + ) + .unwrap() + }) + .map(|option| AutocompleteChoice { + name: option, + value: query.to_string(), + }) + .collect::>() + } else { + debug!("Returning \"Parsed successfully\" response to autocomplete"); + vec![AutocompleteChoice { + name: "Parsed successfully. Send command to view AST.".to_string(), + value: query.to_string(), + }] + } +} + /// Parse a single DRQL query +#[instrument(skip_all, fields(query = query))] #[poise::command(slash_command)] async fn parse_one( ctx: Context<'_>, - #[description = "The DRQL query to parse (DO NOT include @{})"] query: String, + + #[description = "The DRQL query to parse (DO NOT include @{})"] + #[autocomplete = "parse_one_autocomplete"] + query: String, ) -> Result<(), anyhow::Error> { ctx.say(match drql::parser::parse_drql(query.as_str()) { Err(e) => format!("Encountered an error while parsing:\n\n```{e:?}```"), @@ -49,11 +92,64 @@ async fn parse_one( Ok(()) } +#[instrument(skip_all, fields(query = query))] +async fn reduce_autocomplete(_ctx: Context<'_>, query: &str) -> Vec> { + match drql::scanner::scan(query) + .enumerate() + .map(|(n, chunk)| { + drql::parser::parse_drql(chunk).context(format!("Error parsing chunk {n}")) + }) + .collect::, _>>() + { + // The same whitespace printing is done here. First split on newlines, then split on spaces. + Err(e) => { + debug!("Returning parse error response to autocomplete: {e:#}"); + format!("Encountered an error while parsing:\n{e:#}") + .split('\n') + .flat_map(|part| { + crate::util::wrap_string_vec( + &part + .split_whitespace() + .map(std::string::ToString::to_string) + .collect::>(), + " ", + 100, + ) + .unwrap() + }) + .map(|option| AutocompleteChoice { + name: option, + value: query.to_string(), + }) + .collect::>() + } + + Ok(exprs) if exprs.is_empty() => { + debug!("Returning \"No chunks found\" response to autocomplete"); + vec![AutocompleteChoice { + name: "No chunks found.".to_string(), + value: query.to_string(), + }] + } + Ok(_) => { + debug!("Returning \"Parsed successfully\" response to autocomplete"); + vec![AutocompleteChoice { + name: "Parsed successfully. Send command to view reduced AST.".to_string(), + value: query.to_string(), + }] + } + } +} + /// Scan the input, parse each query, and finally reduce into one tree +#[instrument(skip_all, fields(msg = msg))] #[poise::command(slash_command)] async fn reduce( ctx: Context<'_>, - #[description = "The message to scan"] msg: String, + + #[description = "The message to scan"] + #[autocomplete = "reduce_autocomplete"] + msg: String, ) -> Result<(), anyhow::Error> { ctx.say( match drql::scanner::scan(msg.as_str())