added auto_complete for input
This commit is contained in:
parent
20b2c31632
commit
37489944a7
|
@ -20,9 +20,10 @@ macros = { package = "discourse-macros", path = "./discourse-macros" }
|
||||||
ahash = { version = "0.7", optional = true }
|
ahash = { version = "0.7", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
trybuild = "1.0.42"
|
trybuild = { version = "1.0.42", features = ["diff"] }
|
||||||
csscolorparser = "0.4" # examples/input.rs
|
csscolorparser = "0.4" # examples/input.rs
|
||||||
regex = "1.5" # examples/prompt_module.rs
|
regex = "1.5" # examples/prompt_module.rs
|
||||||
|
fuzzy-matcher = "0.3" # examples/file.rs
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["crossterm", "ahash"]
|
default = ["crossterm", "ahash"]
|
||||||
|
|
|
@ -8,13 +8,14 @@ use crate::helpers::*;
|
||||||
|
|
||||||
bitflags::bitflags! {
|
bitflags::bitflags! {
|
||||||
pub struct BuilderMethods: u8 {
|
pub struct BuilderMethods: u8 {
|
||||||
const DEFAULT = 0b000_0001;
|
const DEFAULT = 0b0000_0001;
|
||||||
const TRANSFORM = 0b000_0010;
|
const TRANSFORM = 0b0000_0010;
|
||||||
const VAL_FIL = 0b000_0100;
|
const VAL_FIL = 0b0000_0100;
|
||||||
const LIST = 0b000_1000;
|
const AUTO_COMPLETE = 0b0000_1000;
|
||||||
const MASK = 0b001_0000;
|
const LIST = 0b0001_0000;
|
||||||
const EXTENSION = 0b010_0000;
|
const MASK = 0b0010_0000;
|
||||||
const PLUGIN = 0b100_0000;
|
const EXTENSION = 0b0100_0000;
|
||||||
|
const PLUGIN = 0b1000_0000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +53,13 @@ impl QuestionKind {
|
||||||
|
|
||||||
fn get_builder_methods(&self) -> BuilderMethods {
|
fn get_builder_methods(&self) -> BuilderMethods {
|
||||||
match *self {
|
match *self {
|
||||||
QuestionKind::Input | QuestionKind::Int | QuestionKind::Float => {
|
QuestionKind::Input => {
|
||||||
|
BuilderMethods::DEFAULT
|
||||||
|
| BuilderMethods::TRANSFORM
|
||||||
|
| BuilderMethods::VAL_FIL
|
||||||
|
| BuilderMethods::AUTO_COMPLETE
|
||||||
|
}
|
||||||
|
QuestionKind::Int | QuestionKind::Float => {
|
||||||
BuilderMethods::DEFAULT
|
BuilderMethods::DEFAULT
|
||||||
| BuilderMethods::TRANSFORM
|
| BuilderMethods::TRANSFORM
|
||||||
| BuilderMethods::VAL_FIL
|
| BuilderMethods::VAL_FIL
|
||||||
|
@ -141,6 +148,7 @@ pub(crate) struct QuestionOpts {
|
||||||
pub(crate) validate: Option<syn::Expr>,
|
pub(crate) validate: Option<syn::Expr>,
|
||||||
pub(crate) filter: Option<syn::Expr>,
|
pub(crate) filter: Option<syn::Expr>,
|
||||||
pub(crate) transform: Option<syn::Expr>,
|
pub(crate) transform: Option<syn::Expr>,
|
||||||
|
pub(crate) auto_complete: Option<syn::Expr>,
|
||||||
|
|
||||||
pub(crate) choices: Option<Choices>,
|
pub(crate) choices: Option<Choices>,
|
||||||
pub(crate) page_size: Option<syn::Expr>,
|
pub(crate) page_size: Option<syn::Expr>,
|
||||||
|
@ -164,6 +172,7 @@ impl Default for QuestionOpts {
|
||||||
validate: None,
|
validate: None,
|
||||||
filter: None,
|
filter: None,
|
||||||
transform: None,
|
transform: None,
|
||||||
|
auto_complete: None,
|
||||||
|
|
||||||
choices: None,
|
choices: None,
|
||||||
page_size: None,
|
page_size: None,
|
||||||
|
@ -195,6 +204,9 @@ fn check_disallowed(
|
||||||
ident == "filter") &&
|
ident == "filter") &&
|
||||||
!allowed.contains(BuilderMethods::VAL_FIL)) ||
|
!allowed.contains(BuilderMethods::VAL_FIL)) ||
|
||||||
|
|
||||||
|
((ident == "auto_complete") &&
|
||||||
|
!allowed.contains(BuilderMethods::AUTO_COMPLETE)) ||
|
||||||
|
|
||||||
((ident == "choices" ||
|
((ident == "choices" ||
|
||||||
ident == "page_size" ||
|
ident == "page_size" ||
|
||||||
ident == "should_loop") &&
|
ident == "should_loop") &&
|
||||||
|
@ -269,6 +281,8 @@ impl Parse for Question {
|
||||||
insert_non_dup(ident, &mut opts.filter, &content)?;
|
insert_non_dup(ident, &mut opts.filter, &content)?;
|
||||||
} else if ident == "transform" {
|
} else if ident == "transform" {
|
||||||
insert_non_dup(ident, &mut opts.transform, &content)?;
|
insert_non_dup(ident, &mut opts.transform, &content)?;
|
||||||
|
} else if ident == "auto_complete" {
|
||||||
|
insert_non_dup(ident, &mut opts.auto_complete, &content)?;
|
||||||
} else if ident == "choices" {
|
} else if ident == "choices" {
|
||||||
let parser = match kind {
|
let parser = match kind {
|
||||||
QuestionKind::Checkbox => Choices::parse_checkbox_choice,
|
QuestionKind::Checkbox => Choices::parse_checkbox_choice,
|
||||||
|
@ -382,6 +396,11 @@ impl quote::ToTokens for Question {
|
||||||
quote_spanned! { transform.span() => .transform(#transform) },
|
quote_spanned! { transform.span() => .transform(#transform) },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if let Some(ref auto_complete) = self.opts.auto_complete {
|
||||||
|
tokens.extend(
|
||||||
|
quote_spanned! { auto_complete.span() => .auto_complete(#auto_complete) },
|
||||||
|
);
|
||||||
|
}
|
||||||
if let Some(ref choices) = self.opts.choices {
|
if let Some(ref choices) = self.opts.choices {
|
||||||
tokens.extend(match self.kind {
|
tokens.extend(match self.kind {
|
||||||
QuestionKind::Checkbox => {
|
QuestionKind::Checkbox => {
|
||||||
|
|
|
@ -83,7 +83,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn height(&mut self, _: Layout) -> u16 {
|
fn height(&mut self, _: Layout) -> u16 {
|
||||||
0
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cursor_pos(&mut self, layout: Layout) -> (u16, u16) {
|
fn cursor_pos(&mut self, layout: Layout) -> (u16, u16) {
|
||||||
|
|
|
@ -86,7 +86,7 @@ impl<P: Prompt, B: Backend> Input<P, B> {
|
||||||
};
|
};
|
||||||
|
|
||||||
self.base_row = self.backend.get_cursor()?.1;
|
self.base_row = self.backend.get_cursor()?.1;
|
||||||
let height = self.prompt.height(self.layout());
|
let height = self.prompt.height(self.layout()).saturating_sub(1);
|
||||||
self.base_row = self.adjust_scrollback(height)?;
|
self.base_row = self.adjust_scrollback(height)?;
|
||||||
self.base_col = prompt_len + hint_len;
|
self.base_col = prompt_len + hint_len;
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ impl<P: Prompt, B: Backend> Input<P, B> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn render(&mut self) -> error::Result<()> {
|
pub(super) fn render(&mut self) -> error::Result<()> {
|
||||||
let height = self.prompt.height(self.layout());
|
let height = self.prompt.height(self.layout()).saturating_sub(1);
|
||||||
self.base_row = self.adjust_scrollback(height)?;
|
self.base_row = self.adjust_scrollback(height)?;
|
||||||
self.clear(self.base_col)?;
|
self.clear(self.base_col)?;
|
||||||
self.backend.set_cursor(self.base_col, self.base_row)?;
|
self.backend.set_cursor(self.base_col, self.base_row)?;
|
||||||
|
@ -140,7 +140,7 @@ impl<P: Prompt, B: Backend> Input<P, B> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn goto_last_line(&mut self) -> error::Result<()> {
|
pub(super) fn goto_last_line(&mut self) -> error::Result<()> {
|
||||||
let height = self.prompt.height(self.layout()) + 1;
|
let height = self.prompt.height(self.layout());
|
||||||
self.base_row = self.adjust_scrollback(height)?;
|
self.base_row = self.adjust_scrollback(height)?;
|
||||||
self.backend.set_cursor(0, self.base_row + height as u16)
|
self.backend.set_cursor(0, self.base_row + height as u16)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::{Backend, MoveDirection, Stylize},
|
backend::{Backend, MoveDirection, Stylize},
|
||||||
error,
|
error,
|
||||||
|
@ -58,6 +60,18 @@ pub struct ListPicker<L> {
|
||||||
pub list: L,
|
pub list: L,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<L: Index<usize>> ListPicker<L> {
|
||||||
|
pub fn selected(&self) -> &L::Output {
|
||||||
|
&self.list[self.at]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L: IndexMut<usize>> ListPicker<L> {
|
||||||
|
pub fn selected_mut(&mut self) -> &mut L::Output {
|
||||||
|
&mut self.list[self.at]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<L: List> ListPicker<L> {
|
impl<L: List> ListPicker<L> {
|
||||||
/// Creates a new [`ListPicker`]
|
/// Creates a new [`ListPicker`]
|
||||||
pub fn new(list: L) -> Self {
|
pub fn new(list: L) -> Self {
|
||||||
|
@ -548,7 +562,8 @@ impl<L: List> super::Widget for ListPicker<L> {
|
||||||
self.update_heights(layout);
|
self.update_heights(layout);
|
||||||
|
|
||||||
// Try to show everything
|
// Try to show everything
|
||||||
self.height
|
1 + self // Add one since we go to the next line
|
||||||
|
.height
|
||||||
// otherwise show whatever is possible
|
// otherwise show whatever is possible
|
||||||
.min(self.page_size())
|
.min(self.page_size())
|
||||||
// but do not show less than a single element
|
// but do not show less than a single element
|
||||||
|
@ -559,7 +574,8 @@ impl<L: List> super::Widget for ListPicker<L> {
|
||||||
.heights
|
.heights
|
||||||
.get(self.at)
|
.get(self.at)
|
||||||
.unwrap_or(&0)
|
.unwrap_or(&0)
|
||||||
+ 1, // +1 since the message at the end takes one line
|
// +1 if paginating since the message at the end takes one line
|
||||||
|
+ self.is_paginating() as u16,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,14 @@ impl<F> StringInput<F> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_at(&self) -> usize {
|
||||||
|
self.at
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_at(&mut self, at: usize) {
|
||||||
|
self.at = at.min(self.value_len);
|
||||||
|
}
|
||||||
|
|
||||||
/// The currently inputted value
|
/// The currently inputted value
|
||||||
pub fn value(&self) -> &str {
|
pub fn value(&self) -> &str {
|
||||||
&self.value
|
&self.value
|
||||||
|
@ -70,6 +78,18 @@ impl<F> StringInput<F> {
|
||||||
self.value = value;
|
self.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replaces the value with the result of the function
|
||||||
|
pub fn replace_with<W: FnOnce(String) -> String>(&mut self, with: W) {
|
||||||
|
self.value = with(std::mem::take(&mut self.value));
|
||||||
|
let old_len = self.value_len;
|
||||||
|
self.value_len = self.value.chars().count();
|
||||||
|
if self.at == old_len {
|
||||||
|
self.at = self.value_len;
|
||||||
|
} else {
|
||||||
|
self.set_at(self.at);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Check whether any character has come to the input
|
/// Check whether any character has come to the input
|
||||||
pub fn has_value(&self) -> bool {
|
pub fn has_value(&self) -> bool {
|
||||||
self.value.capacity() > 0
|
self.value.capacity() > 0
|
||||||
|
@ -320,9 +340,9 @@ where
|
||||||
|
|
||||||
fn height(&mut self, layout: Layout) -> u16 {
|
fn height(&mut self, layout: Layout) -> u16 {
|
||||||
if self.value_len as u16 > layout.line_width() {
|
if self.value_len as u16 > layout.line_width() {
|
||||||
1 + (self.value_len as u16 - layout.line_width()) / layout.width
|
2 + (self.value_len as u16 - layout.line_width()) / layout.width
|
||||||
} else {
|
} else {
|
||||||
0
|
1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ pub trait Widget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for &str {
|
impl<T: std::ops::Deref<Target = str>> Widget for T {
|
||||||
/// Does not allow multi-line strings
|
/// Does not allow multi-line strings
|
||||||
fn render<B: Backend>(
|
fn render<B: Backend>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -69,6 +69,6 @@ impl Widget for &str {
|
||||||
|
|
||||||
/// Does not allow multi-line strings
|
/// Does not allow multi-line strings
|
||||||
fn height(&mut self, _: Layout) -> u16 {
|
fn height(&mut self, _: Layout) -> u16 {
|
||||||
0
|
1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
63
examples/file.rs
Normal file
63
examples/file.rs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
||||||
|
|
||||||
|
fn auto_complete(p: String) -> Vec<String> {
|
||||||
|
let current: &Path = p.as_ref();
|
||||||
|
let (mut dir, last) = if p.ends_with('/') {
|
||||||
|
(current, "")
|
||||||
|
} else {
|
||||||
|
let dir = current.parent().unwrap_or_else(|| "/".as_ref());
|
||||||
|
let last = current
|
||||||
|
.file_name()
|
||||||
|
.and_then(std::ffi::OsStr::to_str)
|
||||||
|
.unwrap_or("");
|
||||||
|
(dir, last)
|
||||||
|
};
|
||||||
|
|
||||||
|
if dir.to_str().unwrap().is_empty() {
|
||||||
|
dir = ".".as_ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut files: Vec<_> = match dir.read_dir() {
|
||||||
|
Ok(files) => files
|
||||||
|
.flatten()
|
||||||
|
.flat_map(|file| {
|
||||||
|
let path = file.path();
|
||||||
|
let is_dir = path.is_dir();
|
||||||
|
match path.into_os_string().into_string() {
|
||||||
|
Ok(s) if is_dir => Some(s + "/"),
|
||||||
|
Ok(s) => Some(s),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
Err(_) => return vec![p],
|
||||||
|
};
|
||||||
|
|
||||||
|
if files.is_empty() {
|
||||||
|
return vec![p];
|
||||||
|
} else {
|
||||||
|
let fuzzer = SkimMatcherV2::default();
|
||||||
|
files.sort_by_cached_key(|file| {
|
||||||
|
fuzzer.fuzzy_match(file, last).unwrap_or(i64::MAX)
|
||||||
|
});
|
||||||
|
files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let question = discourse::Question::input("a")
|
||||||
|
.message("Enter a file")
|
||||||
|
.auto_complete(|p, _| auto_complete(p))
|
||||||
|
.validate(|p, _| {
|
||||||
|
if (p.as_ref() as &Path).exists() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format!("file `{}` doesn't exist", p))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
println!("{:#?}", discourse::prompt_one(question));
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
|
use ui::{backend::Color, widgets::List, Widget};
|
||||||
|
|
||||||
use crate::ExpandItem;
|
use crate::ExpandItem;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -67,6 +69,15 @@ impl<T> IndexMut<usize> for ChoiceList<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> std::iter::FromIterator<T> for ChoiceList<T> {
|
||||||
|
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
|
||||||
|
Self {
|
||||||
|
choices: iter.into_iter().map(|c| Choice::Choice(c)).collect(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> Default for ChoiceList<T> {
|
impl<T> Default for ChoiceList<T> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -79,6 +90,54 @@ impl<T> Default for ChoiceList<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Widget> List for ChoiceList<T> {
|
||||||
|
fn render_item<B: ui::backend::Backend>(
|
||||||
|
&mut self,
|
||||||
|
index: usize,
|
||||||
|
hovered: bool,
|
||||||
|
mut layout: ui::Layout,
|
||||||
|
b: &mut B,
|
||||||
|
) -> ui::error::Result<()> {
|
||||||
|
if hovered {
|
||||||
|
b.set_fg(Color::Cyan)?;
|
||||||
|
b.write_all("❯ ".as_bytes())?;
|
||||||
|
} else {
|
||||||
|
b.write_all(b" ")?;
|
||||||
|
|
||||||
|
if !self.is_selectable(index) {
|
||||||
|
b.set_fg(Color::DarkGrey)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layout.offset_x += 2;
|
||||||
|
self.choices[index].render(layout, b)?;
|
||||||
|
|
||||||
|
b.set_fg(Color::Reset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_selectable(&self, index: usize) -> bool {
|
||||||
|
matches!(self.choices[index], Choice::Choice(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_size(&self) -> usize {
|
||||||
|
self.page_size
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_loop(&self) -> bool {
|
||||||
|
self.should_loop
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height_at(&mut self, index: usize, mut layout: ui::Layout) -> u16 {
|
||||||
|
layout.offset_x += 2;
|
||||||
|
|
||||||
|
self[index].height(layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.choices.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Choice<T> {
|
pub enum Choice<T> {
|
||||||
Choice(T),
|
Choice(T),
|
||||||
|
|
|
@ -1,43 +1,122 @@
|
||||||
use ui::{
|
use ui::{
|
||||||
backend::{Backend, Stylize},
|
backend::{Backend, Stylize},
|
||||||
error,
|
error,
|
||||||
events::KeyEvent,
|
events::{KeyCode, KeyEvent},
|
||||||
widgets, Prompt, Validation, Widget,
|
widgets, Prompt, Validation, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{Filter, Options, Transform, Validate};
|
use super::{AutoComplete, ChoiceList, Filter, Options, Transform, Validate};
|
||||||
use crate::{Answer, Answers};
|
use crate::{Answer, Answers};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Input<'i> {
|
pub struct Input<'a> {
|
||||||
default: Option<String>,
|
default: Option<String>,
|
||||||
filter: Filter<'i, String>,
|
filter: Filter<'a, String>,
|
||||||
validate: Validate<'i, str>,
|
validate: Validate<'a, str>,
|
||||||
transform: Transform<'i, str>,
|
transform: Transform<'a, str>,
|
||||||
|
auto_complete: AutoComplete<'a, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InputPrompt<'i, 'a> {
|
struct InputPrompt<'i, 'a> {
|
||||||
message: String,
|
message: String,
|
||||||
input_opts: Input<'i>,
|
input_opts: Input<'i>,
|
||||||
input: widgets::StringInput,
|
input: widgets::StringInput,
|
||||||
|
/// When the picker is Some, then currently the user is selecting from the
|
||||||
|
/// auto complete options. The picker must not be used directly, and instead by used
|
||||||
|
/// through `picker_op`. See `picker_op`s documentation for more.
|
||||||
|
picker: Option<widgets::ListPicker<ChoiceList<String>>>,
|
||||||
answers: &'a Answers,
|
answers: &'a Answers,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Calls a function with the given picker. Anytime the picker is used, it must be used
|
||||||
|
/// through this function. This is is because the selected element of the picker doesn't
|
||||||
|
/// actually contain the element, it is contained by the input. This function
|
||||||
|
/// temporarily swaps the picker's selected item and the input, performs the function,
|
||||||
|
/// and swaps back.
|
||||||
|
fn picker_op<T, F: FnOnce(&mut widgets::ListPicker<ChoiceList<String>>) -> T>(
|
||||||
|
input: &mut widgets::StringInput,
|
||||||
|
picker: &mut widgets::ListPicker<ChoiceList<String>>,
|
||||||
|
op: F,
|
||||||
|
) -> T {
|
||||||
|
let mut res = None;
|
||||||
|
|
||||||
|
input.replace_with(|mut s| {
|
||||||
|
std::mem::swap(&mut s, picker.selected_mut().as_mut().unwrap_choice());
|
||||||
|
res = Some(op(picker));
|
||||||
|
std::mem::swap(&mut s, picker.selected_mut().as_mut().unwrap_choice());
|
||||||
|
s
|
||||||
|
});
|
||||||
|
|
||||||
|
res.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
impl Widget for InputPrompt<'_, '_> {
|
impl Widget for InputPrompt<'_, '_> {
|
||||||
fn render<B: Backend>(
|
fn render<B: Backend>(
|
||||||
&mut self,
|
&mut self,
|
||||||
layout: ui::Layout,
|
layout: ui::Layout,
|
||||||
b: &mut B,
|
b: &mut B,
|
||||||
) -> error::Result<()> {
|
) -> error::Result<()> {
|
||||||
self.input.render(layout, b)
|
self.input.render(layout, b)?;
|
||||||
|
if let Some(ref mut picker) = self.picker {
|
||||||
|
picker_op(&mut self.input, picker, |picker| picker.render(layout, b))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn height(&mut self, layout: ui::Layout) -> u16 {
|
fn height(&mut self, layout: ui::Layout) -> u16 {
|
||||||
self.input.height(layout)
|
let mut height = self.input.height(layout);
|
||||||
|
if let Some(ref mut picker) = self.picker {
|
||||||
|
height +=
|
||||||
|
picker_op(&mut self.input, picker, |picker| picker.height(layout))
|
||||||
|
- 1;
|
||||||
|
}
|
||||||
|
height
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_key(&mut self, key: KeyEvent) -> bool {
|
fn handle_key(&mut self, mut key: KeyEvent) -> bool {
|
||||||
self.input.handle_key(key)
|
let Self {
|
||||||
|
answers,
|
||||||
|
input_opts,
|
||||||
|
input,
|
||||||
|
picker,
|
||||||
|
..
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
match input_opts.auto_complete {
|
||||||
|
AutoComplete::Sync(ref mut ac) if key.code == KeyCode::Tab => {
|
||||||
|
if picker.is_some() {
|
||||||
|
key.code = KeyCode::Down;
|
||||||
|
} else {
|
||||||
|
input.replace_with(|s| {
|
||||||
|
let mut completions = ac(s, answers);
|
||||||
|
assert!(!completions.is_empty());
|
||||||
|
if completions.len() == 1 {
|
||||||
|
completions.pop().unwrap()
|
||||||
|
} else {
|
||||||
|
let res = std::mem::take(&mut completions[0]);
|
||||||
|
|
||||||
|
*picker = Some(widgets::ListPicker::new(
|
||||||
|
completions.into_iter().collect(),
|
||||||
|
));
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.handle_key(key) {
|
||||||
|
*picker = None;
|
||||||
|
true
|
||||||
|
} else if let Some(picker) = picker {
|
||||||
|
picker_op(input, picker, |picker| picker.handle_key(key))
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cursor_pos(&mut self, layout: ui::Layout) -> (u16, u16) {
|
fn cursor_pos(&mut self, layout: ui::Layout) -> (u16, u16) {
|
||||||
|
@ -70,7 +149,13 @@ impl Prompt for InputPrompt<'_, '_> {
|
||||||
|
|
||||||
ans
|
ans
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate(&mut self) -> Result<Validation, Self::ValidateErr> {
|
fn validate(&mut self) -> Result<Validation, Self::ValidateErr> {
|
||||||
|
if self.picker.is_some() {
|
||||||
|
self.picker = None;
|
||||||
|
return Ok(Validation::Continue);
|
||||||
|
}
|
||||||
|
|
||||||
if !self.input.has_value() {
|
if !self.input.has_value() {
|
||||||
if self.has_default() {
|
if self.has_default() {
|
||||||
return Ok(Validation::Finish);
|
return Ok(Validation::Finish);
|
||||||
|
@ -85,9 +170,11 @@ impl Prompt for InputPrompt<'_, '_> {
|
||||||
|
|
||||||
Ok(Validation::Finish)
|
Ok(Validation::Finish)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_default(&self) -> bool {
|
fn has_default(&self) -> bool {
|
||||||
self.input_opts.default.is_some()
|
self.input_opts.default.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish_default(self) -> <Self as ui::Prompt>::Output {
|
fn finish_default(self) -> <Self as ui::Prompt>::Output {
|
||||||
remove_brackets(self.input_opts.default.unwrap())
|
remove_brackets(self.input_opts.default.unwrap())
|
||||||
}
|
}
|
||||||
|
@ -113,6 +200,7 @@ impl Input<'_> {
|
||||||
message,
|
message,
|
||||||
input_opts: self,
|
input_opts: self,
|
||||||
input: widgets::StringInput::default(),
|
input: widgets::StringInput::default(),
|
||||||
|
picker: None,
|
||||||
answers,
|
answers,
|
||||||
},
|
},
|
||||||
b,
|
b,
|
||||||
|
@ -151,6 +239,7 @@ impl<'a> InputBuilder<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::impl_options_builder!();
|
crate::impl_options_builder!();
|
||||||
|
crate::impl_auto_complete_builder!(String; input);
|
||||||
crate::impl_filter_builder!(String; input);
|
crate::impl_filter_builder!(String; input);
|
||||||
crate::impl_validate_builder!(str; input);
|
crate::impl_validate_builder!(str; input);
|
||||||
crate::impl_transform_builder!(str; input);
|
crate::impl_transform_builder!(str; input);
|
||||||
|
|
|
@ -151,7 +151,7 @@ impl Question<'_> {
|
||||||
macro_rules! handler {
|
macro_rules! handler {
|
||||||
($name:ident, $fn_trait:ident ( $($type:ty),* ) -> $return:ty) => {
|
($name:ident, $fn_trait:ident ( $($type:ty),* ) -> $return:ty) => {
|
||||||
pub(crate) enum $name<'a, T> {
|
pub(crate) enum $name<'a, T> {
|
||||||
Sync(Box<dyn $fn_trait( $($type),* ) -> $return + Send + Sync + 'a>),
|
Sync(Box<dyn $fn_trait( $($type),* ) -> $return + 'a>),
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +181,7 @@ macro_rules! handler {
|
||||||
// The type signature of the function must only contain &T
|
// The type signature of the function must only contain &T
|
||||||
($name:ident, ?Sized $fn_trait:ident ( $($type:ty),* ) -> $return:ty) => {
|
($name:ident, ?Sized $fn_trait:ident ( $($type:ty),* ) -> $return:ty) => {
|
||||||
pub(crate) enum $name<'a, T: ?Sized> {
|
pub(crate) enum $name<'a, T: ?Sized> {
|
||||||
Sync(Box<dyn $fn_trait( $($type),* ) -> $return + Send + Sync + 'a>),
|
Sync(Box<dyn $fn_trait( $($type),* ) -> $return + 'a>),
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,6 +210,7 @@ macro_rules! handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
handler!(Filter, FnOnce(T, &Answers) -> T);
|
handler!(Filter, FnOnce(T, &Answers) -> T);
|
||||||
|
handler!(AutoComplete, FnMut(T, &Answers) -> Vec<T>);
|
||||||
handler!(Validate, ?Sized FnMut(&T, &Answers) -> Result<(), String>);
|
handler!(Validate, ?Sized FnMut(&T, &Answers) -> Result<(), String>);
|
||||||
handler!(ValidateByVal, FnMut(T, &Answers) -> Result<(), String>);
|
handler!(ValidateByVal, FnMut(T, &Answers) -> Result<(), String>);
|
||||||
handler!(Transform, ?Sized FnOnce(&T, &Answers, &mut dyn Backend) -> error::Result<()>);
|
handler!(Transform, ?Sized FnOnce(&T, &Answers, &mut dyn Backend) -> error::Result<()>);
|
||||||
|
@ -224,7 +225,7 @@ macro_rules! impl_filter_builder {
|
||||||
($t:ty; $inner:ident) => {
|
($t:ty; $inner:ident) => {
|
||||||
pub fn filter<F>(mut self, filter: F) -> Self
|
pub fn filter<F>(mut self, filter: F) -> Self
|
||||||
where
|
where
|
||||||
F: FnOnce($t, &crate::Answers) -> $t + Send + Sync + 'a,
|
F: FnOnce($t, &crate::Answers) -> $t + 'a,
|
||||||
{
|
{
|
||||||
self.$inner.filter = crate::question::Filter::Sync(Box::new(filter));
|
self.$inner.filter = crate::question::Filter::Sync(Box::new(filter));
|
||||||
self
|
self
|
||||||
|
@ -232,13 +233,28 @@ macro_rules! impl_filter_builder {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! impl_auto_complete_builder {
|
||||||
|
($t:ty; $inner:ident) => {
|
||||||
|
pub fn auto_complete<F>(mut self, auto_complete: F) -> Self
|
||||||
|
where
|
||||||
|
F: FnMut($t, &crate::Answers) -> Vec<$t> + 'a,
|
||||||
|
{
|
||||||
|
self.$inner.auto_complete =
|
||||||
|
crate::question::AutoComplete::Sync(Box::new(auto_complete));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! impl_validate_builder {
|
macro_rules! impl_validate_builder {
|
||||||
($t:ty; $inner:ident) => {
|
($t:ty; $inner:ident) => {
|
||||||
pub fn validate<F>(mut self, filter: F) -> Self
|
pub fn validate<F>(mut self, filter: F) -> Self
|
||||||
where
|
where
|
||||||
F: FnMut(&$t, &crate::Answers) -> Result<(), String> + Send + Sync + 'a,
|
F: FnMut(&$t, &crate::Answers) -> Result<(), String> + 'a,
|
||||||
{
|
{
|
||||||
self.$inner.validate = crate::question::Validate::Sync(Box::new(filter));
|
self.$inner.validate = crate::question::Validate::Sync(Box::new(filter));
|
||||||
self
|
self
|
||||||
|
@ -248,7 +264,7 @@ macro_rules! impl_validate_builder {
|
||||||
(by val $t:ty; $inner:ident) => {
|
(by val $t:ty; $inner:ident) => {
|
||||||
pub fn validate<F>(mut self, filter: F) -> Self
|
pub fn validate<F>(mut self, filter: F) -> Self
|
||||||
where
|
where
|
||||||
F: FnMut($t, &crate::Answers) -> Result<(), String> + Send + Sync + 'a,
|
F: FnMut($t, &crate::Answers) -> Result<(), String> + 'a,
|
||||||
{
|
{
|
||||||
self.$inner.validate =
|
self.$inner.validate =
|
||||||
crate::question::ValidateByVal::Sync(Box::new(filter));
|
crate::question::ValidateByVal::Sync(Box::new(filter));
|
||||||
|
@ -268,8 +284,6 @@ macro_rules! impl_transform_builder {
|
||||||
&crate::Answers,
|
&crate::Answers,
|
||||||
&mut dyn Backend,
|
&mut dyn Backend,
|
||||||
) -> ui::error::Result<()>
|
) -> ui::error::Result<()>
|
||||||
+ Send
|
|
||||||
+ Sync
|
|
||||||
+ 'a,
|
+ 'a,
|
||||||
{
|
{
|
||||||
self.$inner.transform =
|
self.$inner.transform =
|
||||||
|
@ -286,8 +300,6 @@ macro_rules! impl_transform_builder {
|
||||||
&crate::Answers,
|
&crate::Answers,
|
||||||
&mut dyn Backend,
|
&mut dyn Backend,
|
||||||
) -> ui::error::Result<()>
|
) -> ui::error::Result<()>
|
||||||
+ Send
|
|
||||||
+ Sync
|
|
||||||
+ 'a,
|
+ 'a,
|
||||||
{
|
{
|
||||||
self.$inner.transform =
|
self.$inner.transform =
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use ui::{
|
use ui::{
|
||||||
backend::{Backend, Color, Stylize},
|
backend::{Backend, Stylize},
|
||||||
error,
|
error,
|
||||||
events::KeyEvent,
|
events::KeyEvent,
|
||||||
widgets::{self, Text},
|
widgets::{self, Text},
|
||||||
|
@ -85,34 +85,18 @@ impl widgets::List for Select<'_> {
|
||||||
&mut self,
|
&mut self,
|
||||||
index: usize,
|
index: usize,
|
||||||
hovered: bool,
|
hovered: bool,
|
||||||
mut layout: ui::Layout,
|
layout: ui::Layout,
|
||||||
b: &mut B,
|
backend: &mut B,
|
||||||
) -> error::Result<()> {
|
) -> error::Result<()> {
|
||||||
if hovered {
|
self.choices.render_item(index, hovered, layout, backend)
|
||||||
b.set_fg(Color::Cyan)?;
|
|
||||||
b.write_all("❯ ".as_bytes())?;
|
|
||||||
} else {
|
|
||||||
b.write_all(b" ")?;
|
|
||||||
|
|
||||||
if !self.is_selectable(index) {
|
|
||||||
b.set_fg(Color::DarkGrey)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layout.offset_x += 2;
|
|
||||||
self.choices[index].render(layout, b)?;
|
|
||||||
|
|
||||||
b.set_fg(Color::Reset)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_selectable(&self, index: usize) -> bool {
|
fn is_selectable(&self, index: usize) -> bool {
|
||||||
!self.choices[index].is_separator()
|
self.choices.is_selectable(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn height_at(&mut self, index: usize, mut layout: ui::Layout) -> u16 {
|
fn height_at(&mut self, index: usize, layout: ui::Layout) -> u16 {
|
||||||
layout.offset_x += 2;
|
self.choices.height_at(index, layout)
|
||||||
|
|
||||||
self.choices[index].height(layout)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
|
|
|
@ -33,6 +33,7 @@ fn duplicate() {
|
||||||
t.compile_fail("validate");
|
t.compile_fail("validate");
|
||||||
t.compile_fail("filter");
|
t.compile_fail("filter");
|
||||||
t.compile_fail("transform");
|
t.compile_fail("transform");
|
||||||
|
t.compile_fail("auto_complete");
|
||||||
t.compile_fail("choices");
|
t.compile_fail("choices");
|
||||||
t.compile_fail("page_size");
|
t.compile_fail("page_size");
|
||||||
t.compile_fail("should_loop");
|
t.compile_fail("should_loop");
|
||||||
|
@ -62,6 +63,7 @@ fn checkbox() {
|
||||||
t.pass("valid");
|
t.pass("valid");
|
||||||
t.compile_fail("default");
|
t.compile_fail("default");
|
||||||
t.compile_fail("default_with_sep");
|
t.compile_fail("default_with_sep");
|
||||||
|
t.compile_fail("auto_complete");
|
||||||
t.compile_fail("mask");
|
t.compile_fail("mask");
|
||||||
t.compile_fail("extension");
|
t.compile_fail("extension");
|
||||||
t.compile_fail("plugin");
|
t.compile_fail("plugin");
|
||||||
|
@ -74,6 +76,7 @@ fn confirm() {
|
||||||
t.pass("valid");
|
t.pass("valid");
|
||||||
t.compile_fail("filter");
|
t.compile_fail("filter");
|
||||||
t.compile_fail("validate");
|
t.compile_fail("validate");
|
||||||
|
t.compile_fail("auto_complete");
|
||||||
t.compile_fail("choices");
|
t.compile_fail("choices");
|
||||||
t.compile_fail("should_loop");
|
t.compile_fail("should_loop");
|
||||||
t.compile_fail("page_size");
|
t.compile_fail("page_size");
|
||||||
|
@ -87,6 +90,7 @@ fn editor() {
|
||||||
let t = Runner::new("editor");
|
let t = Runner::new("editor");
|
||||||
|
|
||||||
t.pass("valid");
|
t.pass("valid");
|
||||||
|
t.compile_fail("auto_complete");
|
||||||
t.compile_fail("choices");
|
t.compile_fail("choices");
|
||||||
t.compile_fail("should_loop");
|
t.compile_fail("should_loop");
|
||||||
t.compile_fail("page_size");
|
t.compile_fail("page_size");
|
||||||
|
@ -101,6 +105,7 @@ fn expand() {
|
||||||
t.pass("valid");
|
t.pass("valid");
|
||||||
t.compile_fail("filter");
|
t.compile_fail("filter");
|
||||||
t.compile_fail("validate");
|
t.compile_fail("validate");
|
||||||
|
t.compile_fail("auto_complete");
|
||||||
t.compile_fail("mask");
|
t.compile_fail("mask");
|
||||||
t.compile_fail("extension");
|
t.compile_fail("extension");
|
||||||
t.compile_fail("plugin");
|
t.compile_fail("plugin");
|
||||||
|
@ -111,6 +116,7 @@ fn float() {
|
||||||
let t = Runner::new("float");
|
let t = Runner::new("float");
|
||||||
|
|
||||||
t.pass("valid");
|
t.pass("valid");
|
||||||
|
t.compile_fail("auto_complete");
|
||||||
t.compile_fail("choices");
|
t.compile_fail("choices");
|
||||||
t.compile_fail("should_loop");
|
t.compile_fail("should_loop");
|
||||||
t.compile_fail("page_size");
|
t.compile_fail("page_size");
|
||||||
|
@ -137,6 +143,7 @@ fn int() {
|
||||||
let t = Runner::new("int");
|
let t = Runner::new("int");
|
||||||
|
|
||||||
t.pass("valid");
|
t.pass("valid");
|
||||||
|
t.compile_fail("auto_complete");
|
||||||
t.compile_fail("choices");
|
t.compile_fail("choices");
|
||||||
t.compile_fail("should_loop");
|
t.compile_fail("should_loop");
|
||||||
t.compile_fail("page_size");
|
t.compile_fail("page_size");
|
||||||
|
@ -152,6 +159,7 @@ fn select() {
|
||||||
t.pass("valid");
|
t.pass("valid");
|
||||||
t.compile_fail("filter");
|
t.compile_fail("filter");
|
||||||
t.compile_fail("validate");
|
t.compile_fail("validate");
|
||||||
|
t.compile_fail("auto_complete");
|
||||||
t.compile_fail("mask");
|
t.compile_fail("mask");
|
||||||
t.compile_fail("extension");
|
t.compile_fail("extension");
|
||||||
t.compile_fail("plugin");
|
t.compile_fail("plugin");
|
||||||
|
@ -163,6 +171,7 @@ fn password() {
|
||||||
|
|
||||||
t.pass("valid");
|
t.pass("valid");
|
||||||
t.compile_fail("default");
|
t.compile_fail("default");
|
||||||
|
t.compile_fail("auto_complete");
|
||||||
t.compile_fail("choices");
|
t.compile_fail("choices");
|
||||||
t.compile_fail("should_loop");
|
t.compile_fail("should_loop");
|
||||||
t.compile_fail("page_size");
|
t.compile_fail("page_size");
|
||||||
|
@ -179,6 +188,7 @@ fn plugin() {
|
||||||
t.compile_fail("transform");
|
t.compile_fail("transform");
|
||||||
t.compile_fail("filter");
|
t.compile_fail("filter");
|
||||||
t.compile_fail("validate");
|
t.compile_fail("validate");
|
||||||
|
t.compile_fail("auto_complete");
|
||||||
t.compile_fail("choices");
|
t.compile_fail("choices");
|
||||||
t.compile_fail("should_loop");
|
t.compile_fail("should_loop");
|
||||||
t.compile_fail("page_size");
|
t.compile_fail("page_size");
|
||||||
|
@ -193,6 +203,7 @@ fn raw_select() {
|
||||||
t.pass("valid");
|
t.pass("valid");
|
||||||
t.compile_fail("filter");
|
t.compile_fail("filter");
|
||||||
t.compile_fail("validate");
|
t.compile_fail("validate");
|
||||||
|
t.compile_fail("auto_complete");
|
||||||
t.compile_fail("mask");
|
t.compile_fail("mask");
|
||||||
t.compile_fail("extension");
|
t.compile_fail("extension");
|
||||||
t.compile_fail("plugin");
|
t.compile_fail("plugin");
|
||||||
|
|
5
tests/macros/checkbox/auto_complete.rs
Normal file
5
tests/macros/checkbox/auto_complete.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
fn main() {
|
||||||
|
discourse::questions![Checkbox {
|
||||||
|
auto_complete: todo!()
|
||||||
|
}];
|
||||||
|
}
|
5
tests/macros/checkbox/auto_complete.stderr
Normal file
5
tests/macros/checkbox/auto_complete.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: option `auto_complete` does not exist for kind `checkbox`
|
||||||
|
--> $DIR/auto_complete.rs:3:9
|
||||||
|
|
|
||||||
|
3 | auto_complete: todo!()
|
||||||
|
| ^^^^^^^^^^^^^
|
5
tests/macros/confirm/auto_complete.rs
Normal file
5
tests/macros/confirm/auto_complete.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
fn main() {
|
||||||
|
discourse::questions![Confirm {
|
||||||
|
auto_complete: todo!()
|
||||||
|
}];
|
||||||
|
}
|
5
tests/macros/confirm/auto_complete.stderr
Normal file
5
tests/macros/confirm/auto_complete.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: option `auto_complete` does not exist for kind `confirm`
|
||||||
|
--> $DIR/auto_complete.rs:3:9
|
||||||
|
|
|
||||||
|
3 | auto_complete: todo!()
|
||||||
|
| ^^^^^^^^^^^^^
|
6
tests/macros/duplicate/auto_complete.rs
Normal file
6
tests/macros/duplicate/auto_complete.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
fn main() {
|
||||||
|
discourse::questions![Input {
|
||||||
|
auto_complete: todo!(),
|
||||||
|
auto_complete: todo!()
|
||||||
|
}];
|
||||||
|
}
|
5
tests/macros/duplicate/auto_complete.stderr
Normal file
5
tests/macros/duplicate/auto_complete.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: duplicate option `auto_complete`
|
||||||
|
--> $DIR/auto_complete.rs:4:9
|
||||||
|
|
|
||||||
|
4 | auto_complete: todo!()
|
||||||
|
| ^^^^^^^^^^^^^
|
5
tests/macros/editor/auto_complete.rs
Normal file
5
tests/macros/editor/auto_complete.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
fn main() {
|
||||||
|
discourse::questions![Editor {
|
||||||
|
auto_complete: todo!()
|
||||||
|
}];
|
||||||
|
}
|
5
tests/macros/editor/auto_complete.stderr
Normal file
5
tests/macros/editor/auto_complete.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: option `auto_complete` does not exist for kind `editor`
|
||||||
|
--> $DIR/auto_complete.rs:3:9
|
||||||
|
|
|
||||||
|
3 | auto_complete: todo!()
|
||||||
|
| ^^^^^^^^^^^^^
|
5
tests/macros/expand/auto_complete.rs
Normal file
5
tests/macros/expand/auto_complete.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
fn main() {
|
||||||
|
discourse::questions![Expand {
|
||||||
|
auto_complete: todo!()
|
||||||
|
}];
|
||||||
|
}
|
5
tests/macros/expand/auto_complete.stderr
Normal file
5
tests/macros/expand/auto_complete.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: option `auto_complete` does not exist for kind `expand`
|
||||||
|
--> $DIR/auto_complete.rs:3:9
|
||||||
|
|
|
||||||
|
3 | auto_complete: todo!()
|
||||||
|
| ^^^^^^^^^^^^^
|
5
tests/macros/float/auto_complete.rs
Normal file
5
tests/macros/float/auto_complete.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
fn main() {
|
||||||
|
discourse::questions![Float {
|
||||||
|
auto_complete: todo!()
|
||||||
|
}];
|
||||||
|
}
|
5
tests/macros/float/auto_complete.stderr
Normal file
5
tests/macros/float/auto_complete.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: option `auto_complete` does not exist for kind `float`
|
||||||
|
--> $DIR/auto_complete.rs:3:9
|
||||||
|
|
|
||||||
|
3 | auto_complete: todo!()
|
||||||
|
| ^^^^^^^^^^^^^
|
|
@ -5,5 +5,6 @@ fn main() {
|
||||||
transform: |_, _, _| Ok(()),
|
transform: |_, _, _| Ok(()),
|
||||||
validate: |_, _| Ok(()),
|
validate: |_, _| Ok(()),
|
||||||
filter: |t, _| t,
|
filter: |t, _| t,
|
||||||
|
auto_complete: |t, _| vec![t],
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
5
tests/macros/int/auto_complete.rs
Normal file
5
tests/macros/int/auto_complete.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
fn main() {
|
||||||
|
discourse::questions![Int {
|
||||||
|
auto_complete: todo!()
|
||||||
|
}];
|
||||||
|
}
|
5
tests/macros/int/auto_complete.stderr
Normal file
5
tests/macros/int/auto_complete.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: option `auto_complete` does not exist for kind `int`
|
||||||
|
--> $DIR/auto_complete.rs:3:9
|
||||||
|
|
|
||||||
|
3 | auto_complete: todo!()
|
||||||
|
| ^^^^^^^^^^^^^
|
5
tests/macros/password/auto_complete.rs
Normal file
5
tests/macros/password/auto_complete.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
fn main() {
|
||||||
|
discourse::questions![Password {
|
||||||
|
auto_complete: todo!()
|
||||||
|
}];
|
||||||
|
}
|
5
tests/macros/password/auto_complete.stderr
Normal file
5
tests/macros/password/auto_complete.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: option `auto_complete` does not exist for kind `password`
|
||||||
|
--> $DIR/auto_complete.rs:3:9
|
||||||
|
|
|
||||||
|
3 | auto_complete: todo!()
|
||||||
|
| ^^^^^^^^^^^^^
|
5
tests/macros/plugin/auto_complete.rs
Normal file
5
tests/macros/plugin/auto_complete.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
fn main() {
|
||||||
|
discourse::questions![Plugin {
|
||||||
|
auto_complete: todo!()
|
||||||
|
}];
|
||||||
|
}
|
5
tests/macros/plugin/auto_complete.stderr
Normal file
5
tests/macros/plugin/auto_complete.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: option `auto_complete` does not exist for kind `plugin`
|
||||||
|
--> $DIR/auto_complete.rs:3:9
|
||||||
|
|
|
||||||
|
3 | auto_complete: todo!()
|
||||||
|
| ^^^^^^^^^^^^^
|
5
tests/macros/raw_select/auto_complete.rs
Normal file
5
tests/macros/raw_select/auto_complete.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
fn main() {
|
||||||
|
discourse::questions![RawSelect {
|
||||||
|
auto_complete: todo!()
|
||||||
|
}];
|
||||||
|
}
|
5
tests/macros/raw_select/auto_complete.stderr
Normal file
5
tests/macros/raw_select/auto_complete.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: option `auto_complete` does not exist for kind `raw_select`
|
||||||
|
--> $DIR/auto_complete.rs:3:9
|
||||||
|
|
|
||||||
|
3 | auto_complete: todo!()
|
||||||
|
| ^^^^^^^^^^^^^
|
5
tests/macros/select/auto_complete.rs
Normal file
5
tests/macros/select/auto_complete.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
fn main() {
|
||||||
|
discourse::questions![Select {
|
||||||
|
auto_complete: todo!()
|
||||||
|
}];
|
||||||
|
}
|
5
tests/macros/select/auto_complete.stderr
Normal file
5
tests/macros/select/auto_complete.stderr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
error: option `auto_complete` does not exist for kind `select`
|
||||||
|
--> $DIR/auto_complete.rs:3:9
|
||||||
|
|
|
||||||
|
3 | auto_complete: todo!()
|
||||||
|
| ^^^^^^^^^^^^^
|
Loading…
Reference in New Issue
Block a user