added auto_complete for input

This commit is contained in:
Lutetium-Vanadium 2021-05-29 14:52:37 +05:30
parent 20b2c31632
commit 37489944a7
36 changed files with 449 additions and 63 deletions

View File

@ -20,9 +20,10 @@ macros = { package = "discourse-macros", path = "./discourse-macros" }
ahash = { version = "0.7", optional = true }
[dev-dependencies]
trybuild = "1.0.42"
csscolorparser = "0.4" # examples/input.rs
regex = "1.5" # examples/prompt_module.rs
trybuild = { version = "1.0.42", features = ["diff"] }
csscolorparser = "0.4" # examples/input.rs
regex = "1.5" # examples/prompt_module.rs
fuzzy-matcher = "0.3" # examples/file.rs
[features]
default = ["crossterm", "ahash"]

View File

@ -8,13 +8,14 @@ use crate::helpers::*;
bitflags::bitflags! {
pub struct BuilderMethods: u8 {
const DEFAULT = 0b000_0001;
const TRANSFORM = 0b000_0010;
const VAL_FIL = 0b000_0100;
const LIST = 0b000_1000;
const MASK = 0b001_0000;
const EXTENSION = 0b010_0000;
const PLUGIN = 0b100_0000;
const DEFAULT = 0b0000_0001;
const TRANSFORM = 0b0000_0010;
const VAL_FIL = 0b0000_0100;
const AUTO_COMPLETE = 0b0000_1000;
const LIST = 0b0001_0000;
const MASK = 0b0010_0000;
const EXTENSION = 0b0100_0000;
const PLUGIN = 0b1000_0000;
}
}
@ -52,7 +53,13 @@ impl QuestionKind {
fn get_builder_methods(&self) -> BuilderMethods {
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::TRANSFORM
| BuilderMethods::VAL_FIL
@ -141,6 +148,7 @@ pub(crate) struct QuestionOpts {
pub(crate) validate: Option<syn::Expr>,
pub(crate) filter: Option<syn::Expr>,
pub(crate) transform: Option<syn::Expr>,
pub(crate) auto_complete: Option<syn::Expr>,
pub(crate) choices: Option<Choices>,
pub(crate) page_size: Option<syn::Expr>,
@ -164,6 +172,7 @@ impl Default for QuestionOpts {
validate: None,
filter: None,
transform: None,
auto_complete: None,
choices: None,
page_size: None,
@ -195,6 +204,9 @@ fn check_disallowed(
ident == "filter") &&
!allowed.contains(BuilderMethods::VAL_FIL)) ||
((ident == "auto_complete") &&
!allowed.contains(BuilderMethods::AUTO_COMPLETE)) ||
((ident == "choices" ||
ident == "page_size" ||
ident == "should_loop") &&
@ -269,6 +281,8 @@ impl Parse for Question {
insert_non_dup(ident, &mut opts.filter, &content)?;
} else if ident == "transform" {
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" {
let parser = match kind {
QuestionKind::Checkbox => Choices::parse_checkbox_choice,
@ -382,6 +396,11 @@ impl quote::ToTokens for Question {
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 {
tokens.extend(match self.kind {
QuestionKind::Checkbox => {

View File

@ -83,7 +83,7 @@ where
}
fn height(&mut self, _: Layout) -> u16 {
0
1
}
fn cursor_pos(&mut self, layout: Layout) -> (u16, u16) {

View File

@ -86,7 +86,7 @@ impl<P: Prompt, B: Backend> Input<P, B> {
};
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_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<()> {
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.clear(self.base_col)?;
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<()> {
let height = self.prompt.height(self.layout()) + 1;
let height = self.prompt.height(self.layout());
self.base_row = self.adjust_scrollback(height)?;
self.backend.set_cursor(0, self.base_row + height as u16)
}

View File

@ -1,3 +1,5 @@
use std::ops::{Index, IndexMut};
use crate::{
backend::{Backend, MoveDirection, Stylize},
error,
@ -58,6 +60,18 @@ pub struct ListPicker<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> {
/// Creates a new [`ListPicker`]
pub fn new(list: L) -> Self {
@ -548,7 +562,8 @@ impl<L: List> super::Widget for ListPicker<L> {
self.update_heights(layout);
// Try to show everything
self.height
1 + self // Add one since we go to the next line
.height
// otherwise show whatever is possible
.min(self.page_size())
// but do not show less than a single element
@ -559,7 +574,8 @@ impl<L: List> super::Widget for ListPicker<L> {
.heights
.get(self.at)
.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,
)
}
}

View File

@ -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
pub fn value(&self) -> &str {
&self.value
@ -70,6 +78,18 @@ impl<F> StringInput<F> {
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
pub fn has_value(&self) -> bool {
self.value.capacity() > 0
@ -320,9 +340,9 @@ where
fn height(&mut self, layout: Layout) -> u16 {
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 {
0
1
}
}

View File

@ -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
fn render<B: Backend>(
&mut self,
@ -69,6 +69,6 @@ impl Widget for &str {
/// Does not allow multi-line strings
fn height(&mut self, _: Layout) -> u16 {
0
1
}
}

63
examples/file.rs Normal file
View 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));
}

View File

@ -1,5 +1,7 @@
use std::ops::{Index, IndexMut};
use ui::{backend::Color, widgets::List, Widget};
use crate::ExpandItem;
#[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> {
fn default() -> 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)]
pub enum Choice<T> {
Choice(T),

View File

@ -1,43 +1,122 @@
use ui::{
backend::{Backend, Stylize},
error,
events::KeyEvent,
events::{KeyCode, KeyEvent},
widgets, Prompt, Validation, Widget,
};
use super::{Filter, Options, Transform, Validate};
use super::{AutoComplete, ChoiceList, Filter, Options, Transform, Validate};
use crate::{Answer, Answers};
#[derive(Debug, Default)]
pub struct Input<'i> {
pub struct Input<'a> {
default: Option<String>,
filter: Filter<'i, String>,
validate: Validate<'i, str>,
transform: Transform<'i, str>,
filter: Filter<'a, String>,
validate: Validate<'a, str>,
transform: Transform<'a, str>,
auto_complete: AutoComplete<'a, String>,
}
struct InputPrompt<'i, 'a> {
message: String,
input_opts: Input<'i>,
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,
}
#[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<'_, '_> {
fn render<B: Backend>(
&mut self,
layout: ui::Layout,
b: &mut B,
) -> 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 {
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 {
self.input.handle_key(key)
fn handle_key(&mut self, mut key: KeyEvent) -> bool {
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) {
@ -70,7 +149,13 @@ impl Prompt for InputPrompt<'_, '_> {
ans
}
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.has_default() {
return Ok(Validation::Finish);
@ -85,9 +170,11 @@ impl Prompt for InputPrompt<'_, '_> {
Ok(Validation::Finish)
}
fn has_default(&self) -> bool {
self.input_opts.default.is_some()
}
fn finish_default(self) -> <Self as ui::Prompt>::Output {
remove_brackets(self.input_opts.default.unwrap())
}
@ -113,6 +200,7 @@ impl Input<'_> {
message,
input_opts: self,
input: widgets::StringInput::default(),
picker: None,
answers,
},
b,
@ -151,6 +239,7 @@ impl<'a> InputBuilder<'a> {
}
crate::impl_options_builder!();
crate::impl_auto_complete_builder!(String; input);
crate::impl_filter_builder!(String; input);
crate::impl_validate_builder!(str; input);
crate::impl_transform_builder!(str; input);

View File

@ -151,7 +151,7 @@ impl Question<'_> {
macro_rules! handler {
($name:ident, $fn_trait:ident ( $($type:ty),* ) -> $return:ty) => {
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,
}
@ -181,7 +181,7 @@ macro_rules! handler {
// The type signature of the function must only contain &T
($name:ident, ?Sized $fn_trait:ident ( $($type:ty),* ) -> $return:ty) => {
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,
}
@ -210,6 +210,7 @@ macro_rules! handler {
}
handler!(Filter, FnOnce(T, &Answers) -> T);
handler!(AutoComplete, FnMut(T, &Answers) -> Vec<T>);
handler!(Validate, ?Sized FnMut(&T, &Answers) -> Result<(), String>);
handler!(ValidateByVal, FnMut(T, &Answers) -> Result<(), String>);
handler!(Transform, ?Sized FnOnce(&T, &Answers, &mut dyn Backend) -> error::Result<()>);
@ -224,7 +225,7 @@ macro_rules! impl_filter_builder {
($t:ty; $inner:ident) => {
pub fn filter<F>(mut self, filter: F) -> Self
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
@ -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)]
#[macro_export]
macro_rules! impl_validate_builder {
($t:ty; $inner:ident) => {
pub fn validate<F>(mut self, filter: F) -> Self
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
@ -248,7 +264,7 @@ macro_rules! impl_validate_builder {
(by val $t:ty; $inner:ident) => {
pub fn validate<F>(mut self, filter: F) -> Self
where
F: FnMut($t, &crate::Answers) -> Result<(), String> + Send + Sync + 'a,
F: FnMut($t, &crate::Answers) -> Result<(), String> + 'a,
{
self.$inner.validate =
crate::question::ValidateByVal::Sync(Box::new(filter));
@ -268,8 +284,6 @@ macro_rules! impl_transform_builder {
&crate::Answers,
&mut dyn Backend,
) -> ui::error::Result<()>
+ Send
+ Sync
+ 'a,
{
self.$inner.transform =
@ -286,8 +300,6 @@ macro_rules! impl_transform_builder {
&crate::Answers,
&mut dyn Backend,
) -> ui::error::Result<()>
+ Send
+ Sync
+ 'a,
{
self.$inner.transform =

View File

@ -1,5 +1,5 @@
use ui::{
backend::{Backend, Color, Stylize},
backend::{Backend, Stylize},
error,
events::KeyEvent,
widgets::{self, Text},
@ -85,34 +85,18 @@ impl widgets::List for Select<'_> {
&mut self,
index: usize,
hovered: bool,
mut layout: ui::Layout,
b: &mut B,
layout: ui::Layout,
backend: &mut B,
) -> 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)
self.choices.render_item(index, hovered, layout, backend)
}
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 {
layout.offset_x += 2;
self.choices[index].height(layout)
fn height_at(&mut self, index: usize, layout: ui::Layout) -> u16 {
self.choices.height_at(index, layout)
}
fn len(&self) -> usize {

View File

@ -33,6 +33,7 @@ fn duplicate() {
t.compile_fail("validate");
t.compile_fail("filter");
t.compile_fail("transform");
t.compile_fail("auto_complete");
t.compile_fail("choices");
t.compile_fail("page_size");
t.compile_fail("should_loop");
@ -62,6 +63,7 @@ fn checkbox() {
t.pass("valid");
t.compile_fail("default");
t.compile_fail("default_with_sep");
t.compile_fail("auto_complete");
t.compile_fail("mask");
t.compile_fail("extension");
t.compile_fail("plugin");
@ -74,6 +76,7 @@ fn confirm() {
t.pass("valid");
t.compile_fail("filter");
t.compile_fail("validate");
t.compile_fail("auto_complete");
t.compile_fail("choices");
t.compile_fail("should_loop");
t.compile_fail("page_size");
@ -87,6 +90,7 @@ fn editor() {
let t = Runner::new("editor");
t.pass("valid");
t.compile_fail("auto_complete");
t.compile_fail("choices");
t.compile_fail("should_loop");
t.compile_fail("page_size");
@ -101,6 +105,7 @@ fn expand() {
t.pass("valid");
t.compile_fail("filter");
t.compile_fail("validate");
t.compile_fail("auto_complete");
t.compile_fail("mask");
t.compile_fail("extension");
t.compile_fail("plugin");
@ -111,6 +116,7 @@ fn float() {
let t = Runner::new("float");
t.pass("valid");
t.compile_fail("auto_complete");
t.compile_fail("choices");
t.compile_fail("should_loop");
t.compile_fail("page_size");
@ -137,6 +143,7 @@ fn int() {
let t = Runner::new("int");
t.pass("valid");
t.compile_fail("auto_complete");
t.compile_fail("choices");
t.compile_fail("should_loop");
t.compile_fail("page_size");
@ -152,6 +159,7 @@ fn select() {
t.pass("valid");
t.compile_fail("filter");
t.compile_fail("validate");
t.compile_fail("auto_complete");
t.compile_fail("mask");
t.compile_fail("extension");
t.compile_fail("plugin");
@ -163,6 +171,7 @@ fn password() {
t.pass("valid");
t.compile_fail("default");
t.compile_fail("auto_complete");
t.compile_fail("choices");
t.compile_fail("should_loop");
t.compile_fail("page_size");
@ -179,6 +188,7 @@ fn plugin() {
t.compile_fail("transform");
t.compile_fail("filter");
t.compile_fail("validate");
t.compile_fail("auto_complete");
t.compile_fail("choices");
t.compile_fail("should_loop");
t.compile_fail("page_size");
@ -193,6 +203,7 @@ fn raw_select() {
t.pass("valid");
t.compile_fail("filter");
t.compile_fail("validate");
t.compile_fail("auto_complete");
t.compile_fail("mask");
t.compile_fail("extension");
t.compile_fail("plugin");

View File

@ -0,0 +1,5 @@
fn main() {
discourse::questions![Checkbox {
auto_complete: todo!()
}];
}

View 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!()
| ^^^^^^^^^^^^^

View File

@ -0,0 +1,5 @@
fn main() {
discourse::questions![Confirm {
auto_complete: todo!()
}];
}

View 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!()
| ^^^^^^^^^^^^^

View File

@ -0,0 +1,6 @@
fn main() {
discourse::questions![Input {
auto_complete: todo!(),
auto_complete: todo!()
}];
}

View File

@ -0,0 +1,5 @@
error: duplicate option `auto_complete`
--> $DIR/auto_complete.rs:4:9
|
4 | auto_complete: todo!()
| ^^^^^^^^^^^^^

View File

@ -0,0 +1,5 @@
fn main() {
discourse::questions![Editor {
auto_complete: todo!()
}];
}

View 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!()
| ^^^^^^^^^^^^^

View File

@ -0,0 +1,5 @@
fn main() {
discourse::questions![Expand {
auto_complete: todo!()
}];
}

View 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!()
| ^^^^^^^^^^^^^

View File

@ -0,0 +1,5 @@
fn main() {
discourse::questions![Float {
auto_complete: todo!()
}];
}

View 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!()
| ^^^^^^^^^^^^^

View File

@ -5,5 +5,6 @@ fn main() {
transform: |_, _, _| Ok(()),
validate: |_, _| Ok(()),
filter: |t, _| t,
auto_complete: |t, _| vec![t],
}];
}

View File

@ -0,0 +1,5 @@
fn main() {
discourse::questions![Int {
auto_complete: todo!()
}];
}

View 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!()
| ^^^^^^^^^^^^^

View File

@ -0,0 +1,5 @@
fn main() {
discourse::questions![Password {
auto_complete: todo!()
}];
}

View 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!()
| ^^^^^^^^^^^^^

View File

@ -0,0 +1,5 @@
fn main() {
discourse::questions![Plugin {
auto_complete: todo!()
}];
}

View 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!()
| ^^^^^^^^^^^^^

View File

@ -0,0 +1,5 @@
fn main() {
discourse::questions![RawSelect {
auto_complete: todo!()
}];
}

View 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!()
| ^^^^^^^^^^^^^

View File

@ -0,0 +1,5 @@
fn main() {
discourse::questions![Select {
auto_complete: todo!()
}];
}

View 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!()
| ^^^^^^^^^^^^^