Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: autocomplete in /debug commands #48

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
100 changes: 98 additions & 2 deletions src/commands/debug.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use anyhow::{bail, Context as _};
use poise::AutocompleteChoice;
use tracing::{debug, instrument};

use super::super::{drql, Context};

Expand Down Expand Up @@ -34,11 +36,52 @@ async fn scan(
Ok(())
}

#[instrument(skip_all, fields(query = query))]
async fn parse_one_autocomplete(_ctx: Context<'_>, query: &str) -> Vec<AutocompleteChoice<String>> {
// 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::<Vec<_>>(),
" ",
100,
)
.unwrap()
})
.map(|option| AutocompleteChoice {
name: option,
value: query.to_string(),
})
.collect::<Vec<_>>()
} 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:?}```"),
Expand All @@ -49,11 +92,64 @@ async fn parse_one(
Ok(())
}

#[instrument(skip_all, fields(query = query))]
async fn reduce_autocomplete(_ctx: Context<'_>, query: &str) -> Vec<AutocompleteChoice<String>> {
match drql::scanner::scan(query)
.enumerate()
.map(|(n, chunk)| {
drql::parser::parse_drql(chunk).context(format!("Error parsing chunk {n}"))
})
.collect::<Result<Vec<_>, _>>()
{
// 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::<Vec<_>>(),
" ",
100,
)
.unwrap()
})
.map(|option| AutocompleteChoice {
name: option,
value: query.to_string(),
})
.collect::<Vec<_>>()
}

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())
Expand Down