<commit_before>use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::rc::Rc;

use syntex_syntax::ast;
use syntex_syntax::attr;
use syntex_syntax::codemap::{DUMMY_SP, respan};
use syntex_syntax::ext::base::{
    ExtCtxt,
    IdentMacroExpander,
    MultiItemDecorator,
    MultiItemModifier,
    NamedSyntaxExtension,
    Resolver,
    SyntaxExtension,
    TTMacroExpander,
};
use syntex_syntax::ext::expand;
use syntex_syntax::feature_gate;
use syntex_syntax::parse;
use syntex_syntax::parse::token::intern;
use syntex_syntax::print::pprust;
use syntex_syntax::ptr::P;

use super::resolver;
use super::error::Error;

pub type Pass = fn(ast::Crate) -> ast::Crate;

pub struct Registry {
    syntax_exts: Vec<NamedSyntaxExtension>,
    pre_expansion_passes: Vec<Box<Pass>>,
    post_expansion_passes: Vec<Box<Pass>>,
    cfg: Vec<P<ast::MetaItem>>,
    attrs: Vec<ast::Attribute>,
}

impl Registry {
    pub fn new() -> Registry {
        Registry {
            syntax_exts: Vec::new(),
            pre_expansion_passes: Vec::new(),
            post_expansion_passes: Vec::new(),
            cfg: Vec::new(),
            attrs: Vec::new(),
        }
    }

    pub fn add_cfg(&mut self, cfg: &str) {
        let parse_sess = parse::ParseSess::new();
        let meta_item = parse::parse_meta_from_source_str(
            "cfgspec".to_string(),
            cfg.to_string(),
            Vec::new(),
            &parse_sess).unwrap();

        self.cfg.push(meta_item);
    }

    pub fn add_attr(&mut self, attr: &str) {
        let parse_sess = parse::ParseSess::new();
        let meta_item = parse::parse_meta_from_source_str(
            "attrspec".to_string(),
            attr.to_string(),
            Vec::new(),
            &parse_sess).unwrap();

        self.attrs.push(respan(DUMMY_SP, ast::Attribute_ {
            id: attr::mk_attr_id(),
            style: ast::AttrStyle::Outer,
            value: meta_item,
            is_sugared_doc: false,
        }));
    }

    pub fn add_macro<F>(&mut self, name: &str, extension: F)
        where F: TTMacroExpander + 'static
    {
        let name = intern(name);
        let syntax_extension = SyntaxExtension::NormalTT(
            Box::new(extension),
            None,
            false
        );
        self.syntax_exts.push((name, syntax_extension));
    }

    pub fn add_ident_macro<F>(&mut self, name: &str, extension: F)
        where F: IdentMacroExpander + 'static
    {
        let name = intern(name);
        let syntax_extension = SyntaxExtension::IdentTT(
            Box::new(extension),
            None,
            false
        );
        self.syntax_exts.push((name, syntax_extension));
    }

    pub fn add_decorator<F>(&mut self, name: &str, extension: F)
        where F: MultiItemDecorator + 'static
    {
        let name = intern(name);
        let syntax_extension = SyntaxExtension::MultiDecorator(Box::new(extension));
        self.syntax_exts.push((name, syntax_extension));
    }

    pub fn add_modifier<F>(&mut self, name: &str, extension: F)
        where F: MultiItemModifier + 'static
    {
        let name = intern(name);
        let syntax_extension = SyntaxExtension::MultiModifier(Box::new(extension));
        self.syntax_exts.push((name, syntax_extension));
    }

    pub fn add_pre_expansion_pass(&mut self, pass: Pass) {
        self.pre_expansion_passes.push(Box::new(pass))
    }

    pub fn add_post_expansion_pass(&mut self, pass: Pass) {
        self.post_expansion_passes.push(Box::new(pass))
    }

    pub fn expand<S, D>(self, crate_name: &str, src: S, dst: D) -> Result<(), Error>
        where S: AsRef<Path>,
              D: AsRef<Path>,
    {
        let src = src.as_ref();
        let dst = dst.as_ref();

        let sess = parse::ParseSess::new();

        let krate = try!(parse::parse_crate_from_file(
            src,
            self.cfg.clone(),
            &sess));

        if sess.span_diagnostic.has_errors() {
            return Err(Error::Parse);
        }

        let src_name = src.to_str().unwrap().to_string();

        let out = try!(self.expand_crate(
                crate_name,
                &sess,
                src_name,
                krate));

        if sess.span_diagnostic.has_errors() {
            return Err(Error::Expand);
        }

        let mut dst = try!(File::create(dst));
        try!(dst.write_all(&out));
        Ok(())
    }

    /// This method will expand all macros in the source string `src`, and return the results in a
    /// string.
    pub fn expand_str(self,
                      crate_name: &str,
                      src_name: &str,
                      src: &str) -> Result<String, Error> {
        let sess = parse::ParseSess::new();

        let src_name = src_name.to_owned();

        let krate = try!(parse::parse_crate_from_source_str(
            src_name.clone(),
            src.to_owned(),
            self.cfg.clone(),
            &sess));

        let out = try!(self.expand_crate(crate_name, &sess, src_name, krate));

        Ok(String::from_utf8(out).unwrap())
    }

    fn expand_crate(self,
                    crate_name: &str,
                    sess: &parse::ParseSess,
                    src_name: String,
                    mut krate: ast::Crate) -> Result<Vec<u8>, Error> {
        krate.attrs.extend(self.attrs.iter().cloned());

        let features = feature_gate::get_features(
            &sess.span_diagnostic,
            &krate.attrs);

        let krate = self.pre_expansion_passes.iter()
            .fold(krate, |krate, f| (f)(krate));

        let mut ecfg = expand::ExpansionConfig::default(crate_name.to_owned());
        ecfg.features = Some(&features);

        let cfg = Vec::new();
        let mut resolver = resolver::Resolver::new(sess);

        for (name, ext) in self.syntax_exts {
            let ident = ast::Ident::with_empty_ctxt(name);
            resolver.add_ext(ident, Rc::new(ext));
        }

        let mut ecx = ExtCtxt::new(&sess, cfg, ecfg, &mut resolver);
        let krate = ecx.monotonic_expander().expand_crate(krate);

        let krate = self.post_expansion_passes.iter()
            .fold(krate, |krate, f| (f)(krate));

        let src = sess.codemap()
            .get_filemap(&src_name)
            .unwrap()
            .src
            .as_ref()
            .unwrap()
            .as_bytes()
            .to_vec();

        let mut rdr = &src[..];

        let mut out = Vec::new();
        let annotation = pprust::NoAnn;

        try!(pprust::print_crate(
            sess.codemap(),
            &sess.span_diagnostic,
            &krate,
            src_name,
            &mut rdr,
            Box::new(&mut out),
            &annotation,
            false));

        Ok(out)
    }
}
<commit_msg>Fix parser api changes<commit_after>use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::rc::Rc;

use syntex_syntax::ast;
use syntex_syntax::attr;
use syntex_syntax::codemap::{DUMMY_SP, respan};
use syntex_syntax::ext::base::{
    ExtCtxt,
    IdentMacroExpander,
    MultiItemDecorator,
    MultiItemModifier,
    NamedSyntaxExtension,
    Resolver,
    SyntaxExtension,
    TTMacroExpander,
};
use syntex_syntax::ext::expand;
use syntex_syntax::feature_gate;
use syntex_syntax::parse;
use syntex_syntax::parse::token::intern;
use syntex_syntax::print::pprust;
use syntex_syntax::ptr::P;

use super::resolver;
use super::error::Error;

pub type Pass = fn(ast::Crate) -> ast::Crate;

pub struct Registry {
    syntax_exts: Vec<NamedSyntaxExtension>,
    pre_expansion_passes: Vec<Box<Pass>>,
    post_expansion_passes: Vec<Box<Pass>>,
    cfg: Vec<P<ast::MetaItem>>,
    attrs: Vec<ast::Attribute>,
}

impl Registry {
    pub fn new() -> Registry {
        Registry {
            syntax_exts: Vec::new(),
            pre_expansion_passes: Vec::new(),
            post_expansion_passes: Vec::new(),
            cfg: Vec::new(),
            attrs: Vec::new(),
        }
    }

    pub fn add_cfg(&mut self, cfg: &str) {
        let parse_sess = parse::ParseSess::new();
        let meta_item = parse::parse_meta_from_source_str(
            "cfgspec".to_string(),
            cfg.to_string(),
            &parse_sess).unwrap();

        self.cfg.push(meta_item);
    }

    pub fn add_attr(&mut self, attr: &str) {
        let parse_sess = parse::ParseSess::new();
        let meta_item = parse::parse_meta_from_source_str(
            "attrspec".to_string(),
            attr.to_string(),
            &parse_sess).unwrap();

        self.attrs.push(respan(DUMMY_SP, ast::Attribute_ {
            id: attr::mk_attr_id(),
            style: ast::AttrStyle::Outer,
            value: meta_item,
            is_sugared_doc: false,
        }));
    }

    pub fn add_macro<F>(&mut self, name: &str, extension: F)
        where F: TTMacroExpander + 'static
    {
        let name = intern(name);
        let syntax_extension = SyntaxExtension::NormalTT(
            Box::new(extension),
            None,
            false
        );
        self.syntax_exts.push((name, syntax_extension));
    }

    pub fn add_ident_macro<F>(&mut self, name: &str, extension: F)
        where F: IdentMacroExpander + 'static
    {
        let name = intern(name);
        let syntax_extension = SyntaxExtension::IdentTT(
            Box::new(extension),
            None,
            false
        );
        self.syntax_exts.push((name, syntax_extension));
    }

    pub fn add_decorator<F>(&mut self, name: &str, extension: F)
        where F: MultiItemDecorator + 'static
    {
        let name = intern(name);
        let syntax_extension = SyntaxExtension::MultiDecorator(Box::new(extension));
        self.syntax_exts.push((name, syntax_extension));
    }

    pub fn add_modifier<F>(&mut self, name: &str, extension: F)
        where F: MultiItemModifier + 'static
    {
        let name = intern(name);
        let syntax_extension = SyntaxExtension::MultiModifier(Box::new(extension));
        self.syntax_exts.push((name, syntax_extension));
    }

    pub fn add_pre_expansion_pass(&mut self, pass: Pass) {
        self.pre_expansion_passes.push(Box::new(pass))
    }

    pub fn add_post_expansion_pass(&mut self, pass: Pass) {
        self.post_expansion_passes.push(Box::new(pass))
    }

    pub fn expand<S, D>(self, crate_name: &str, src: S, dst: D) -> Result<(), Error>
        where S: AsRef<Path>,
              D: AsRef<Path>,
    {
        let src = src.as_ref();
        let dst = dst.as_ref();

        let sess = parse::ParseSess::new();

        let krate = try!(parse::parse_crate_from_file(
            src,
            &sess));

        if sess.span_diagnostic.has_errors() {
            return Err(Error::Parse);
        }

        let src_name = src.to_str().unwrap().to_string();

        let out = try!(self.expand_crate(
                crate_name,
                &sess,
                src_name,
                krate));

        if sess.span_diagnostic.has_errors() {
            return Err(Error::Expand);
        }

        let mut dst = try!(File::create(dst));
        try!(dst.write_all(&out));
        Ok(())
    }

    /// This method will expand all macros in the source string `src`, and return the results in a
    /// string.
    pub fn expand_str(self,
                      crate_name: &str,
                      src_name: &str,
                      src: &str) -> Result<String, Error> {
        let sess = parse::ParseSess::new();

        let src_name = src_name.to_owned();

        let krate = try!(parse::parse_crate_from_source_str(
            src_name.clone(),
            src.to_owned(),
            &sess));

        let out = try!(self.expand_crate(crate_name, &sess, src_name, krate));

        Ok(String::from_utf8(out).unwrap())
    }

    fn expand_crate(self,
                    crate_name: &str,
                    sess: &parse::ParseSess,
                    src_name: String,
                    mut krate: ast::Crate) -> Result<Vec<u8>, Error> {
        krate.attrs.extend(self.attrs.iter().cloned());

        let features = feature_gate::get_features(
            &sess.span_diagnostic,
            &krate.attrs);

        let krate = self.pre_expansion_passes.iter()
            .fold(krate, |krate, f| (f)(krate));

        let mut ecfg = expand::ExpansionConfig::default(crate_name.to_owned());
        ecfg.features = Some(&features);

        let mut resolver = resolver::Resolver::new(sess);

        for (name, ext) in self.syntax_exts {
            let ident = ast::Ident::with_empty_ctxt(name);
            resolver.add_ext(ident, Rc::new(ext));
        }

        let mut ecx = ExtCtxt::new(&sess, ecfg, &mut resolver);
        let krate = ecx.monotonic_expander().expand_crate(krate);

        let krate = self.post_expansion_passes.iter()
            .fold(krate, |krate, f| (f)(krate));

        let src = sess.codemap()
            .get_filemap(&src_name)
            .unwrap()
            .src
            .as_ref()
            .unwrap()
            .as_bytes()
            .to_vec();

        let mut rdr = &src[..];

        let mut out = Vec::new();
        let annotation = pprust::NoAnn;

        try!(pprust::print_crate(
            sess.codemap(),
            &sess.span_diagnostic,
            &krate,
            src_name,
            &mut rdr,
            Box::new(&mut out),
            &annotation,
            false));

        Ok(out)
    }
}
<|endoftext|>


<commit_before>//! A small TCP server to handle collection of diagnostics information in a
//! cross-platform way for the `cargo fix` command.

use std::collections::HashSet;
use std::env;
use std::io::{BufReader, Read, Write};
use std::net::{Shutdown, SocketAddr, TcpListener, TcpStream};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread::{self, JoinHandle};

use failure::{Error, ResultExt};
use serde_json;

use util::{Config, ProcessBuilder};
use util::errors::CargoResult;

const DIAGNOSICS_SERVER_VAR: &str = "__CARGO_FIX_DIAGNOSTICS_SERVER";
const PLEASE_REPORT_THIS_BUG: &str =
    "\
     This likely indicates a bug in either rustc or cargo itself,\n\
     and we would appreciate a bug report! You're likely to see \n\
     a number of compiler warnings after this message which cargo\n\
     attempted to fix but failed. If you could open an issue at\n\
     https://github.com/rust-lang/cargo/issues\n\
     quoting the full output of this command we'd be very appreciative!\n\n\
     ";

#[derive(Deserialize, Serialize)]
pub enum Message {
    Fixing {
        file: String,
        fixes: u32,
    },
    FixFailed {
        files: Vec<String>,
        krate: Option<String>,
    },
    ReplaceFailed {
        file: String,
        message: String,
    },
    PreviewNotFound {
        file: String,
        edition: String,
    },
    EditionAlreadyEnabled {
        file: String,
        edition: String,
    },
    IdiomEditionMismatch {
        file: String,
        idioms: String,
        edition: Option<String>,
    },
}

impl Message {
    pub fn post(&self) -> Result<(), Error> {
        let addr = env::var(DIAGNOSICS_SERVER_VAR)
            .context("diagnostics collector misconfigured")?;
        let mut client =
            TcpStream::connect(&addr).context("failed to connect to parent diagnostics target")?;

        let s = serde_json::to_string(self).context("failed to serialize message")?;
        client
            .write_all(s.as_bytes())
            .context("failed to write message to diagnostics target")?;
        client
            .shutdown(Shutdown::Write)
            .context("failed to shutdown")?;

        let mut tmp = Vec::new();
        client
            .read_to_end(&mut tmp)
            .context("failed to receive a disconnect")?;

        Ok(())
    }
}

pub struct DiagnosticPrinter<'a> {
    config: &'a Config,
    preview_not_found: HashSet<String>,
    edition_already_enabled: HashSet<String>,
    idiom_mismatch: HashSet<String>,
}

impl<'a> DiagnosticPrinter<'a> {
    pub fn new(config: &'a Config) -> DiagnosticPrinter<'a> {
        DiagnosticPrinter {
            config,
            preview_not_found: HashSet::new(),
            edition_already_enabled: HashSet::new(),
            idiom_mismatch: HashSet::new(),
        }
    }

    pub fn print(&mut self, msg: &Message) -> CargoResult<()> {
        match msg {
            Message::Fixing { file, fixes } => {
                let msg = if *fixes == 1 { "fix" } else { "fixes" };
                let msg = format!("{} ({} {})", file, fixes, msg);
                self.config.shell().status("Fixing", msg)
            }
            Message::ReplaceFailed { file, message } => {
                let msg = format!("error applying suggestions to `{}`\n", file);
                self.config.shell().warn(&msg)?;
                write!(
                    self.config.shell().err(),
                    "The full error message was:\n\n> {}\n\n",
                    message,
                )?;
                write!(self.config.shell().err(), "{}", PLEASE_REPORT_THIS_BUG)?;
                Ok(())
            }
            Message::FixFailed { files, krate } => {
                if let Some(ref krate) = *krate {
                    self.config.shell().warn(&format!(
                        "failed to automatically apply fixes suggested by rustc \
                         to crate `{}`",
                        krate,
                    ))?;
                } else {
                    self.config.shell().warn(
                        "failed to automatically apply fixes suggested by rustc"
                    )?;
                }
                if !files.is_empty() {
                    writeln!(
                        self.config.shell().err(),
                        "\nafter fixes were automatically applied the compiler \
                         reported errors within these files:\n"
                    )?;
                    for file in files {
                        writeln!(self.config.shell().err(), "  * {}", file)?;
                    }
                    writeln!(self.config.shell().err())?;
                }
                write!(self.config.shell().err(), "{}", PLEASE_REPORT_THIS_BUG)?;
                Ok(())
            }
            Message::PreviewNotFound { file, edition } => {
                // By default we're fixing a lot of things concurrently, don't
                // warn about the same file multiple times.
                if !self.preview_not_found.insert(file.clone()) {
                    return Ok(())
                }
                self.config.shell().warn(&format!(
                    "failed to find `#![feature(rust_{}_preview)]` in `{}`\n\
                     this may cause `cargo fix` to not be able to fix all\n\
                     issues in preparation for the {0} edition",
                    edition,
                    file,
                ))?;
                Ok(())
            }
            Message::EditionAlreadyEnabled { file, edition } => {
                // Like above, only warn once per file
                if !self.edition_already_enabled.insert(file.clone()) {
                    return Ok(())
                }

                let msg = format!(
                    "\
cannot prepare for the {} edition when it is enabled, so cargo cannot
automatically fix errors in `{}`

To prepare for the {0} edition you should first remove `edition = '{0}'` from
your `Cargo.toml` and then rerun this command. Once all warnings have been fixed
then you can re-enable the `edition` key in `Cargo.toml`. For some more
information about transitioning to the {0} edition see:

  https://rust-lang-nursery.github.io/edition-guide/editions/transitioning.html
",
                    edition,
                    file,
                );
                self.config.shell().error(&msg)?;
                Ok(())
            }
            Message::IdiomEditionMismatch { file, idioms, edition } => {
                // Same as above
                if !self.idiom_mismatch.insert(file.clone()) {
                    return Ok(())
                }
                self.config.shell().error(&format!(
                    "\
cannot migrate to the idioms of the {} edition for `{}`
because it is compiled {}, which doesn't match {0}

consider migrating to the {0} edition by adding `edition = '{0}'` to
`Cargo.toml` and then rerunning this command; a more detailed transition
guide can be found at

  https://rust-lang-nursery.github.io/edition-guide/editions/transitioning-your-code-to-a-new-edition.html
",
                    idioms,
                    file,
                    match edition {
                        Some(s) => format!("with the {} edition", s),
                        None => "without an edition".to_string(),
                    },
                ))?;
                Ok(())
            }
        }
    }
}

#[derive(Debug)]
pub struct RustfixDiagnosticServer {
    listener: TcpListener,
    addr: SocketAddr,
}

pub struct StartedServer {
    addr: SocketAddr,
    done: Arc<AtomicBool>,
    thread: Option<JoinHandle<()>>,
}

impl RustfixDiagnosticServer {
    pub fn new() -> Result<Self, Error> {
        let listener = TcpListener::bind("127.0.0.1:0")
            .with_context(|_| "failed to bind TCP listener to manage locking")?;
        let addr = listener.local_addr()?;

        Ok(RustfixDiagnosticServer { listener, addr })
    }

    pub fn configure(&self, process: &mut ProcessBuilder) {
        process.env(DIAGNOSICS_SERVER_VAR, self.addr.to_string());
    }

    pub fn start<F>(self, on_message: F) -> Result<StartedServer, Error>
    where
        F: Fn(Message) + Send + 'static,
    {
        let addr = self.addr;
        let done = Arc::new(AtomicBool::new(false));
        let done2 = done.clone();
        let thread = thread::spawn(move || {
            self.run(&on_message, &done2);
        });

        Ok(StartedServer {
            addr,
            thread: Some(thread),
            done,
        })
    }

    fn run(self, on_message: &Fn(Message), done: &AtomicBool) {
        while let Ok((client, _)) = self.listener.accept() {
            let client = BufReader::new(client);
            match serde_json::from_reader(client) {
                Ok(message) => on_message(message),
                Err(e) => warn!("invalid diagnostics message: {}", e),
            }
            if done.load(Ordering::SeqCst) {
                break
            }
        }
    }
}

impl Drop for StartedServer {
    fn drop(&mut self) {
        self.done.store(true, Ordering::SeqCst);
        // Ignore errors here as this is largely best-effort
        if TcpStream::connect(&self.addr).is_err() {
            return;
        }
        drop(self.thread.take().unwrap().join());
    }
}
<commit_msg>Fix broken url in diagnostic.<commit_after>//! A small TCP server to handle collection of diagnostics information in a
//! cross-platform way for the `cargo fix` command.

use std::collections::HashSet;
use std::env;
use std::io::{BufReader, Read, Write};
use std::net::{Shutdown, SocketAddr, TcpListener, TcpStream};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread::{self, JoinHandle};

use failure::{Error, ResultExt};
use serde_json;

use util::{Config, ProcessBuilder};
use util::errors::CargoResult;

const DIAGNOSICS_SERVER_VAR: &str = "__CARGO_FIX_DIAGNOSTICS_SERVER";
const PLEASE_REPORT_THIS_BUG: &str =
    "\
     This likely indicates a bug in either rustc or cargo itself,\n\
     and we would appreciate a bug report! You're likely to see \n\
     a number of compiler warnings after this message which cargo\n\
     attempted to fix but failed. If you could open an issue at\n\
     https://github.com/rust-lang/cargo/issues\n\
     quoting the full output of this command we'd be very appreciative!\n\n\
     ";

#[derive(Deserialize, Serialize)]
pub enum Message {
    Fixing {
        file: String,
        fixes: u32,
    },
    FixFailed {
        files: Vec<String>,
        krate: Option<String>,
    },
    ReplaceFailed {
        file: String,
        message: String,
    },
    PreviewNotFound {
        file: String,
        edition: String,
    },
    EditionAlreadyEnabled {
        file: String,
        edition: String,
    },
    IdiomEditionMismatch {
        file: String,
        idioms: String,
        edition: Option<String>,
    },
}

impl Message {
    pub fn post(&self) -> Result<(), Error> {
        let addr = env::var(DIAGNOSICS_SERVER_VAR)
            .context("diagnostics collector misconfigured")?;
        let mut client =
            TcpStream::connect(&addr).context("failed to connect to parent diagnostics target")?;

        let s = serde_json::to_string(self).context("failed to serialize message")?;
        client
            .write_all(s.as_bytes())
            .context("failed to write message to diagnostics target")?;
        client
            .shutdown(Shutdown::Write)
            .context("failed to shutdown")?;

        let mut tmp = Vec::new();
        client
            .read_to_end(&mut tmp)
            .context("failed to receive a disconnect")?;

        Ok(())
    }
}

pub struct DiagnosticPrinter<'a> {
    config: &'a Config,
    preview_not_found: HashSet<String>,
    edition_already_enabled: HashSet<String>,
    idiom_mismatch: HashSet<String>,
}

impl<'a> DiagnosticPrinter<'a> {
    pub fn new(config: &'a Config) -> DiagnosticPrinter<'a> {
        DiagnosticPrinter {
            config,
            preview_not_found: HashSet::new(),
            edition_already_enabled: HashSet::new(),
            idiom_mismatch: HashSet::new(),
        }
    }

    pub fn print(&mut self, msg: &Message) -> CargoResult<()> {
        match msg {
            Message::Fixing { file, fixes } => {
                let msg = if *fixes == 1 { "fix" } else { "fixes" };
                let msg = format!("{} ({} {})", file, fixes, msg);
                self.config.shell().status("Fixing", msg)
            }
            Message::ReplaceFailed { file, message } => {
                let msg = format!("error applying suggestions to `{}`\n", file);
                self.config.shell().warn(&msg)?;
                write!(
                    self.config.shell().err(),
                    "The full error message was:\n\n> {}\n\n",
                    message,
                )?;
                write!(self.config.shell().err(), "{}", PLEASE_REPORT_THIS_BUG)?;
                Ok(())
            }
            Message::FixFailed { files, krate } => {
                if let Some(ref krate) = *krate {
                    self.config.shell().warn(&format!(
                        "failed to automatically apply fixes suggested by rustc \
                         to crate `{}`",
                        krate,
                    ))?;
                } else {
                    self.config.shell().warn(
                        "failed to automatically apply fixes suggested by rustc"
                    )?;
                }
                if !files.is_empty() {
                    writeln!(
                        self.config.shell().err(),
                        "\nafter fixes were automatically applied the compiler \
                         reported errors within these files:\n"
                    )?;
                    for file in files {
                        writeln!(self.config.shell().err(), "  * {}", file)?;
                    }
                    writeln!(self.config.shell().err())?;
                }
                write!(self.config.shell().err(), "{}", PLEASE_REPORT_THIS_BUG)?;
                Ok(())
            }
            Message::PreviewNotFound { file, edition } => {
                // By default we're fixing a lot of things concurrently, don't
                // warn about the same file multiple times.
                if !self.preview_not_found.insert(file.clone()) {
                    return Ok(())
                }
                self.config.shell().warn(&format!(
                    "failed to find `#![feature(rust_{}_preview)]` in `{}`\n\
                     this may cause `cargo fix` to not be able to fix all\n\
                     issues in preparation for the {0} edition",
                    edition,
                    file,
                ))?;
                Ok(())
            }
            Message::EditionAlreadyEnabled { file, edition } => {
                // Like above, only warn once per file
                if !self.edition_already_enabled.insert(file.clone()) {
                    return Ok(())
                }

                let msg = format!(
                    "\
cannot prepare for the {} edition when it is enabled, so cargo cannot
automatically fix errors in `{}`

To prepare for the {0} edition you should first remove `edition = '{0}'` from
your `Cargo.toml` and then rerun this command. Once all warnings have been fixed
then you can re-enable the `edition` key in `Cargo.toml`. For some more
information about transitioning to the {0} edition see:

  https://rust-lang-nursery.github.io/edition-guide/editions/transitioning-your-code-to-a-new-edition.html
",
                    edition,
                    file,
                );
                self.config.shell().error(&msg)?;
                Ok(())
            }
            Message::IdiomEditionMismatch { file, idioms, edition } => {
                // Same as above
                if !self.idiom_mismatch.insert(file.clone()) {
                    return Ok(())
                }
                self.config.shell().error(&format!(
                    "\
cannot migrate to the idioms of the {} edition for `{}`
because it is compiled {}, which doesn't match {0}

consider migrating to the {0} edition by adding `edition = '{0}'` to
`Cargo.toml` and then rerunning this command; a more detailed transition
guide can be found at

  https://rust-lang-nursery.github.io/edition-guide/editions/transitioning-your-code-to-a-new-edition.html
",
                    idioms,
                    file,
                    match edition {
                        Some(s) => format!("with the {} edition", s),
                        None => "without an edition".to_string(),
                    },
                ))?;
                Ok(())
            }
        }
    }
}

#[derive(Debug)]
pub struct RustfixDiagnosticServer {
    listener: TcpListener,
    addr: SocketAddr,
}

pub struct StartedServer {
    addr: SocketAddr,
    done: Arc<AtomicBool>,
    thread: Option<JoinHandle<()>>,
}

impl RustfixDiagnosticServer {
    pub fn new() -> Result<Self, Error> {
        let listener = TcpListener::bind("127.0.0.1:0")
            .with_context(|_| "failed to bind TCP listener to manage locking")?;
        let addr = listener.local_addr()?;

        Ok(RustfixDiagnosticServer { listener, addr })
    }

    pub fn configure(&self, process: &mut ProcessBuilder) {
        process.env(DIAGNOSICS_SERVER_VAR, self.addr.to_string());
    }

    pub fn start<F>(self, on_message: F) -> Result<StartedServer, Error>
    where
        F: Fn(Message) + Send + 'static,
    {
        let addr = self.addr;
        let done = Arc::new(AtomicBool::new(false));
        let done2 = done.clone();
        let thread = thread::spawn(move || {
            self.run(&on_message, &done2);
        });

        Ok(StartedServer {
            addr,
            thread: Some(thread),
            done,
        })
    }

    fn run(self, on_message: &Fn(Message), done: &AtomicBool) {
        while let Ok((client, _)) = self.listener.accept() {
            let client = BufReader::new(client);
            match serde_json::from_reader(client) {
                Ok(message) => on_message(message),
                Err(e) => warn!("invalid diagnostics message: {}", e),
            }
            if done.load(Ordering::SeqCst) {
                break
            }
        }
    }
}

impl Drop for StartedServer {
    fn drop(&mut self) {
        self.done.store(true, Ordering::SeqCst);
        // Ignore errors here as this is largely best-effort
        if TcpStream::connect(&self.addr).is_err() {
            return;
        }
        drop(self.thread.take().unwrap().join());
    }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use std::fmt::Display;

use serde::de::{MapAccess, Unexpected, Visitor};
use serde::{Deserialize, Deserializer};
use term_input::{Arrow, FKey, Key};

#[derive(Debug, PartialEq)]
pub(crate) struct KeyMap(HashMap<Key, KeyAction>);

#[derive(Debug, Copy, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub(crate) enum KeyAction {
    Cancel,
    Disable,
    Exit,

    RunEditor,

    TabNext,
    TabPrev,
    TabMoveLeft,
    TabMoveRight,
    TabGoto(char),

    MessagesPageUp,
    MessagesPageDown,
    MessagesScrollUp,
    MessagesScrollDown,
    MessagesScrollTop,
    MessagesScrollBottom,

    Input(char),
    InputAutoComplete,
    InputNextEntry,
    InputPrevEntry,
    InputSend,
    InputDeletePrevChar,
    InputDeleteNextChar,
    InputDeleteToStart,
    InputDeleteToEnd,
    InputDeletePrevWord,
    InputMoveCursEnd,
    InputMoveCursStart,
    InputMoveCursLeft,
    InputMoveCursRight,
    InputMoveWordLeft,
    InputMoveWordRight,
}

impl Display for KeyAction {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let s = match self {
            KeyAction::Cancel => "cancel",
            KeyAction::Disable => "disable",
            KeyAction::Exit => "exit",
            KeyAction::RunEditor => "run_editor",
            KeyAction::TabNext => "tab_next",
            KeyAction::TabPrev => "tab_prev",
            KeyAction::TabMoveLeft => "tab_move_left",
            KeyAction::TabMoveRight => "tab_move_right",
            KeyAction::TabGoto(c) => return writeln!(f, "tab_goto: {}", c),
            KeyAction::MessagesPageUp => "messages_page_up",
            KeyAction::MessagesPageDown => "messages_page_down",
            KeyAction::MessagesScrollUp => "messages_scroll_up",
            KeyAction::MessagesScrollDown => "messages_scroll_down",
            KeyAction::MessagesScrollTop => "messages_scroll_top",
            KeyAction::MessagesScrollBottom => "messages_scroll_bottom",
            KeyAction::Input(c) => return writeln!(f, "input_{}", c),
            KeyAction::InputAutoComplete => "input_auto_complete",
            KeyAction::InputNextEntry => "input_next_entry",
            KeyAction::InputPrevEntry => "input_prev_entry",
            KeyAction::InputSend => "input_send",
            KeyAction::InputDeletePrevChar => "input_delete_prev_char",
            KeyAction::InputDeleteNextChar => "input_delete_next_char",
            KeyAction::InputDeleteToStart => "input_delete_to_start",
            KeyAction::InputDeleteToEnd => "input_delete_to_end",
            KeyAction::InputDeletePrevWord => "input_delete_prev_word",
            KeyAction::InputMoveCursEnd => "input_move_curs_end",
            KeyAction::InputMoveCursStart => "input_move_curs_start",
            KeyAction::InputMoveCursLeft => "input_move_curs_left",
            KeyAction::InputMoveCursRight => "input_move_curs_right",
            KeyAction::InputMoveWordLeft => "input_move_word_left",
            KeyAction::InputMoveWordRight => "input_move_word_right",
        };
        writeln!(f, "{}", s)
    }
}

impl Default for KeyMap {
    fn default() -> Self {
        let map = vec![
            (Key::Esc, KeyAction::Cancel),
            (Key::Ctrl('c'), KeyAction::Exit),
            (Key::Ctrl('x'), KeyAction::RunEditor),
            (Key::Ctrl('n'), KeyAction::TabNext),
            (Key::Ctrl('p'), KeyAction::TabPrev),
            (Key::CtrlArrow(Arrow::Left), KeyAction::TabMoveLeft),
            (Key::CtrlArrow(Arrow::Left), KeyAction::TabMoveRight),
            (Key::AltChar('1'), KeyAction::TabGoto('1')),
            (Key::AltChar('2'), KeyAction::TabGoto('2')),
            (Key::AltChar('3'), KeyAction::TabGoto('3')),
            (Key::AltChar('4'), KeyAction::TabGoto('4')),
            (Key::AltChar('5'), KeyAction::TabGoto('5')),
            (Key::AltChar('6'), KeyAction::TabGoto('6')),
            (Key::AltChar('7'), KeyAction::TabGoto('7')),
            (Key::AltChar('8'), KeyAction::TabGoto('8')),
            (Key::AltChar('9'), KeyAction::TabGoto('9')),
            (Key::AltChar('0'), KeyAction::TabGoto('0')),
            (Key::Ctrl('u'), KeyAction::MessagesPageUp),
            (Key::Ctrl('d'), KeyAction::MessagesPageDown),
            (Key::PageUp, KeyAction::MessagesPageUp),
            (Key::PageDown, KeyAction::MessagesPageDown),
            (Key::ShiftArrow(Arrow::Up), KeyAction::MessagesScrollUp),
            (Key::ShiftArrow(Arrow::Down), KeyAction::MessagesScrollDown),
            (Key::Home, KeyAction::MessagesScrollTop),
            (Key::End, KeyAction::MessagesScrollBottom),
            (Key::Tab, KeyAction::InputAutoComplete),
            (Key::Arrow(Arrow::Up), KeyAction::InputPrevEntry),
            (Key::Arrow(Arrow::Down), KeyAction::InputNextEntry),
            (Key::Char('\r'), KeyAction::InputSend),
            (Key::Backspace, KeyAction::InputDeletePrevChar),
            (Key::Del, KeyAction::InputDeleteNextChar),
            (Key::Ctrl('a'), KeyAction::InputMoveCursStart),
            (Key::Ctrl('e'), KeyAction::InputMoveCursEnd),
            (Key::Ctrl('k'), KeyAction::InputDeleteToEnd),
            (Key::Ctrl('w'), KeyAction::InputDeletePrevWord),
            (Key::Arrow(Arrow::Left), KeyAction::InputMoveCursLeft),
            (Key::Arrow(Arrow::Right), KeyAction::InputMoveCursRight),
            (Key::CtrlArrow(Arrow::Left), KeyAction::InputMoveWordLeft),
            (Key::CtrlArrow(Arrow::Right), KeyAction::InputMoveWordRight),
        ];
        let hash_map = map.into_iter().collect::<HashMap<_, _>>();
        KeyMap(hash_map)
    }
}

impl Display for KeyMap {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for (key, action) in self.0.iter() {
            writeln!(f, "key: {:?}, action: {}", key, action)?;
        }
        Ok(())
    }
}

impl KeyMap {
    pub(crate) fn get(&self, key: &Key) -> Option<KeyAction> {
        self.0.get(key).cloned()
    }

    pub(crate) fn load(&mut self, key_map: &KeyMap) {
        self.0.extend(key_map.0.iter())
    }
}

impl<'de> Deserialize<'de> for KeyMap {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        struct KeyMapVisitor;

        impl<'de> Visitor<'de> for KeyMapVisitor {
            type Value = KeyMap;

            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(
                    formatter,
                    "a map of key bindings. ex. 'ctrl_a: input_move_curs_start'"
                )?;
                write!(formatter, "defaults: \n{}", KeyMap::default())
            }

            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
            where
                A: MapAccess<'de>,
            {
                let mut key_map = HashMap::new();
                while let Some((key, action)) = map.next_entry::<MappedKey, KeyAction>()? {
                    key_map.insert(key.0, action);
                }
                Ok(KeyMap(key_map))
            }
        }
        deserializer.deserialize_map(KeyMapVisitor)
    }
}

#[derive(Debug)]
pub(crate) struct MappedKey(Key);

impl<'de> Deserialize<'de> for MappedKey {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct MappedKeyVisitor;

        impl<'de> Visitor<'de> for MappedKeyVisitor {
            type Value = MappedKey;

            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(formatter, "single keys: backspace, del, end, esc, home, pgdown, pgup, tab, up, down, left right, [a-z], [0-9], [f1-f12]. ")?;
                write!(
                    formatter,
                    "modifiers with an arrow key, function key, or single characters:  alt, shift, ctrl"
                )
            }

            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                let key_combo = v.split_once('_');
                let single_key = |s: &str| {
                    let mut chars = s.chars();
                    if let (Some(c), None) = (chars.next(), chars.next()) {
                        Ok(c)
                    } else {
                        Err(E::invalid_value(Unexpected::Str(s), &Self))
                    }
                };

                let key = match key_combo {
                    None => match v {
                        "backspace" => Key::Backspace,
                        "del" => Key::Del,
                        "end" => Key::End,
                        "esc" => Key::Esc,
                        "home" => Key::Home,
                        "pgdown" => Key::PageDown,
                        "pgup" => Key::PageUp,
                        "tab" => Key::Tab,
                        other => {
                            if let Some(arrow) = parse_arrow(other) {
                                Key::Arrow(arrow)
                            } else if let Some(f_key) = parse_f_key(other) {
                                Key::FKey(f_key)
                            } else {
                                Key::Char(single_key(other)?)
                            }
                        }
                    },
                    Some((k1, k2)) => match k1 {
                        "alt" => {
                            if let Some(arrow) = parse_arrow(k2) {
                                Key::AltArrow(arrow)
                            } else if let Some(f_key) = parse_f_key(k2) {
                                Key::AltF(f_key)
                            } else {
                                Key::AltChar(single_key(k2)?)
                            }
                        }
                        "ctrl" => {
                            if let Some(arrow) = parse_arrow(k2) {
                                Key::CtrlArrow(arrow)
                            } else if let Some(f_key) = parse_f_key(k2) {
                                Key::CtrlF(f_key)
                            } else {
                                Key::Ctrl(single_key(k2)?)
                            }
                        }
                        "shift" => {
                            if let Some(arrow) = parse_arrow(k2) {
                                Key::ShiftArrow(arrow)
                            } else if let Some(f_key) = parse_f_key(k2) {
                                Key::ShiftF(f_key)
                            } else {
                                return Err(E::invalid_value(Unexpected::Str(k2), &Self));
                            }
                        }
                        unexp => return Err(E::invalid_value(Unexpected::Str(unexp), &Self)),
                    },
                };

                if term_input::is_valid_key(key) {
                    Ok(MappedKey(key))
                } else {
                    Err(E::invalid_value(Unexpected::Str(v), &Self))
                }
            }
        }

        deserializer.deserialize_str(MappedKeyVisitor)
    }
}

fn parse_arrow(s: &str) -> Option<Arrow> {
    match s {
        "up" => Some(Arrow::Up),
        "down" => Some(Arrow::Down),
        "left" => Some(Arrow::Left),
        "right" => Some(Arrow::Right),
        _ => None,
    }
}

fn parse_f_key(s: &str) -> Option<FKey> {
    match s {
        "f1" => Some(FKey::F1),
        "f2" => Some(FKey::F2),
        "f3" => Some(FKey::F3),
        "f4" => Some(FKey::F4),
        "f5" => Some(FKey::F5),
        "f6" => Some(FKey::F6),
        "f7" => Some(FKey::F7),
        "f8" => Some(FKey::F8),
        "f9" => Some(FKey::F9),
        "f10" => Some(FKey::F10),
        "f11" => Some(FKey::F11),
        "f12" => Some(FKey::F12),
        _ => None,
    }
}

#[test]
fn deser_key() {
    let s = "alt_p";
    let key: MappedKey = serde_yaml::from_str(s).unwrap();
    assert_eq!(Key::AltChar('p'), key.0);
    let s = "alt_è";
    let key: MappedKey = serde_yaml::from_str(s).unwrap();
    assert_eq!(Key::AltChar('è'), key.0);
    let s = "alt__";
    let key: MappedKey = serde_yaml::from_str(s).unwrap();
    assert_eq!(Key::AltChar('_'), key.0);
    let s = "ctrl_/";
    let key: MappedKey = serde_yaml::from_str(s).unwrap();
    assert_eq!(Key::Ctrl('/'), key.0);
    let s = "ctrl_f2";
    let key: MappedKey = serde_yaml::from_str(s).unwrap();
    assert_eq!(Key::CtrlF(FKey::F2), key.0);
    let s = "alt_f2";
    let key: MappedKey = serde_yaml::from_str(s).unwrap();
    assert_eq!(Key::AltF(FKey::F2), key.0);
    let s = "shift_f2";
    let key: MappedKey = serde_yaml::from_str(s).unwrap();
    assert_eq!(Key::ShiftF(FKey::F2), key.0);
    let s = "shift_left";
    let key: MappedKey = serde_yaml::from_str(s).unwrap();
    assert_eq!(Key::ShiftArrow(Arrow::Left), key.0);
}

#[test]
fn deser_key_action_goto_tab() {
    let s = "tab_goto: 1";
    let a = serde_yaml::from_str::<KeyAction>(s).unwrap();
    assert_eq!(KeyAction::TabGoto('1'), a);
}

#[test]
fn deser_keymap() {
    let s = r#"
    ctrl_a: input_move_curs_start
    ctrl_e:
      tab_goto: 1
    a:
      input: b
    "#;
    let mut expect = KeyMap(HashMap::new());
    expect
        .0
        .insert(Key::Ctrl('a'), KeyAction::InputMoveCursStart);
    expect.0.insert(Key::Ctrl('e'), KeyAction::TabGoto('1'));
    expect.0.insert(Key::Char('a'), KeyAction::Input('b'));

    let key_map: KeyMap = serde_yaml::from_str(s).unwrap();
    assert_eq!(expect, key_map);
}
<commit_msg>Fix default tab-move-left/right bindings<commit_after>use std::collections::HashMap;
use std::fmt::Display;

use serde::de::{MapAccess, Unexpected, Visitor};
use serde::{Deserialize, Deserializer};
use term_input::{Arrow, FKey, Key};

#[derive(Debug, PartialEq)]
pub(crate) struct KeyMap(HashMap<Key, KeyAction>);

#[derive(Debug, Copy, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub(crate) enum KeyAction {
    Cancel,
    Disable,
    Exit,

    RunEditor,

    TabNext,
    TabPrev,
    TabMoveLeft,
    TabMoveRight,
    TabGoto(char),

    MessagesPageUp,
    MessagesPageDown,
    MessagesScrollUp,
    MessagesScrollDown,
    MessagesScrollTop,
    MessagesScrollBottom,

    Input(char),
    InputAutoComplete,
    InputNextEntry,
    InputPrevEntry,
    InputSend,
    InputDeletePrevChar,
    InputDeleteNextChar,
    InputDeleteToStart,
    InputDeleteToEnd,
    InputDeletePrevWord,
    InputMoveCursEnd,
    InputMoveCursStart,
    InputMoveCursLeft,
    InputMoveCursRight,
    InputMoveWordLeft,
    InputMoveWordRight,
}

impl Display for KeyAction {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let s = match self {
            KeyAction::Cancel => "cancel",
            KeyAction::Disable => "disable",
            KeyAction::Exit => "exit",
            KeyAction::RunEditor => "run_editor",
            KeyAction::TabNext => "tab_next",
            KeyAction::TabPrev => "tab_prev",
            KeyAction::TabMoveLeft => "tab_move_left",
            KeyAction::TabMoveRight => "tab_move_right",
            KeyAction::TabGoto(c) => return writeln!(f, "tab_goto: {}", c),
            KeyAction::MessagesPageUp => "messages_page_up",
            KeyAction::MessagesPageDown => "messages_page_down",
            KeyAction::MessagesScrollUp => "messages_scroll_up",
            KeyAction::MessagesScrollDown => "messages_scroll_down",
            KeyAction::MessagesScrollTop => "messages_scroll_top",
            KeyAction::MessagesScrollBottom => "messages_scroll_bottom",
            KeyAction::Input(c) => return writeln!(f, "input_{}", c),
            KeyAction::InputAutoComplete => "input_auto_complete",
            KeyAction::InputNextEntry => "input_next_entry",
            KeyAction::InputPrevEntry => "input_prev_entry",
            KeyAction::InputSend => "input_send",
            KeyAction::InputDeletePrevChar => "input_delete_prev_char",
            KeyAction::InputDeleteNextChar => "input_delete_next_char",
            KeyAction::InputDeleteToStart => "input_delete_to_start",
            KeyAction::InputDeleteToEnd => "input_delete_to_end",
            KeyAction::InputDeletePrevWord => "input_delete_prev_word",
            KeyAction::InputMoveCursEnd => "input_move_curs_end",
            KeyAction::InputMoveCursStart => "input_move_curs_start",
            KeyAction::InputMoveCursLeft => "input_move_curs_left",
            KeyAction::InputMoveCursRight => "input_move_curs_right",
            KeyAction::InputMoveWordLeft => "input_move_word_left",
            KeyAction::InputMoveWordRight => "input_move_word_right",
        };
        writeln!(f, "{}", s)
    }
}

impl Default for KeyMap {
    fn default() -> Self {
        let map = vec![
            (Key::Esc, KeyAction::Cancel),
            (Key::Ctrl('c'), KeyAction::Exit),
            (Key::Ctrl('x'), KeyAction::RunEditor),
            (Key::Ctrl('n'), KeyAction::TabNext),
            (Key::Ctrl('p'), KeyAction::TabPrev),
            (Key::AltArrow(Arrow::Left), KeyAction::TabMoveLeft),
            (Key::AltArrow(Arrow::Right), KeyAction::TabMoveRight),
            (Key::AltChar('1'), KeyAction::TabGoto('1')),
            (Key::AltChar('2'), KeyAction::TabGoto('2')),
            (Key::AltChar('3'), KeyAction::TabGoto('3')),
            (Key::AltChar('4'), KeyAction::TabGoto('4')),
            (Key::AltChar('5'), KeyAction::TabGoto('5')),
            (Key::AltChar('6'), KeyAction::TabGoto('6')),
            (Key::AltChar('7'), KeyAction::TabGoto('7')),
            (Key::AltChar('8'), KeyAction::TabGoto('8')),
            (Key::AltChar('9'), KeyAction::TabGoto('9')),
            (Key::AltChar('0'), KeyAction::TabGoto('0')),
            (Key::Ctrl('u'), KeyAction::MessagesPageUp),
            (Key::Ctrl('d'), KeyAction::MessagesPageDown),
            (Key::PageUp, KeyAction::MessagesPageUp),
            (Key::PageDown, KeyAction::MessagesPageDown),
            (Key::ShiftArrow(Arrow::Up), KeyAction::MessagesScrollUp),
            (Key::ShiftArrow(Arrow::Down), KeyAction::MessagesScrollDown),
            (Key::Home, KeyAction::MessagesScrollTop),
            (Key::End, KeyAction::MessagesScrollBottom),
            (Key::Tab, KeyAction::InputAutoComplete),
            (Key::Arrow(Arrow::Up), KeyAction::InputPrevEntry),
            (Key::Arrow(Arrow::Down), KeyAction::InputNextEntry),
            (Key::Char('\r'), KeyAction::InputSend),
            (Key::Backspace, KeyAction::InputDeletePrevChar),
            (Key::Del, KeyAction::InputDeleteNextChar),
            (Key::Ctrl('a'), KeyAction::InputMoveCursStart),
            (Key::Ctrl('e'), KeyAction::InputMoveCursEnd),
            (Key::Ctrl('k'), KeyAction::InputDeleteToEnd),
            (Key::Ctrl('w'), KeyAction::InputDeletePrevWord),
            (Key::Arrow(Arrow::Left), KeyAction::InputMoveCursLeft),
            (Key::Arrow(Arrow::Right), KeyAction::InputMoveCursRight),
            (Key::CtrlArrow(Arrow::Left), KeyAction::InputMoveWordLeft),
            (Key::CtrlArrow(Arrow::Right), KeyAction::InputMoveWordRight),
        ];
        let hash_map = map.into_iter().collect::<HashMap<_, _>>();
        KeyMap(hash_map)
    }
}

impl Display for KeyMap {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for (key, action) in self.0.iter() {
            writeln!(f, "key: {:?}, action: {}", key, action)?;
        }
        Ok(())
    }
}

impl KeyMap {
    pub(crate) fn get(&self, key: &Key) -> Option<KeyAction> {
        self.0.get(key).cloned()
    }

    pub(crate) fn load(&mut self, key_map: &KeyMap) {
        self.0.extend(key_map.0.iter())
    }
}

impl<'de> Deserialize<'de> for KeyMap {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        struct KeyMapVisitor;

        impl<'de> Visitor<'de> for KeyMapVisitor {
            type Value = KeyMap;

            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(
                    formatter,
                    "a map of key bindings. ex. 'ctrl_a: input_move_curs_start'"
                )?;
                write!(formatter, "defaults: \n{}", KeyMap::default())
            }

            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
            where
                A: MapAccess<'de>,
            {
                let mut key_map = HashMap::new();
                while let Some((key, action)) = map.next_entry::<MappedKey, KeyAction>()? {
                    key_map.insert(key.0, action);
                }
                Ok(KeyMap(key_map))
            }
        }
        deserializer.deserialize_map(KeyMapVisitor)
    }
}

#[derive(Debug)]
pub(crate) struct MappedKey(Key);

impl<'de> Deserialize<'de> for MappedKey {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct MappedKeyVisitor;

        impl<'de> Visitor<'de> for MappedKeyVisitor {
            type Value = MappedKey;

            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(formatter, "single keys: backspace, del, end, esc, home, pgdown, pgup, tab, up, down, left right, [a-z], [0-9], [f1-f12]. ")?;
                write!(
                    formatter,
                    "modifiers with an arrow key, function key, or single characters:  alt, shift, ctrl"
                )
            }

            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                let key_combo = v.split_once('_');
                let single_key = |s: &str| {
                    let mut chars = s.chars();
                    if let (Some(c), None) = (chars.next(), chars.next()) {
                        Ok(c)
                    } else {
                        Err(E::invalid_value(Unexpected::Str(s), &Self))
                    }
                };

                let key = match key_combo {
                    None => match v {
                        "backspace" => Key::Backspace,
                        "del" => Key::Del,
                        "end" => Key::End,
                        "esc" => Key::Esc,
                        "home" => Key::Home,
                        "pgdown" => Key::PageDown,
                        "pgup" => Key::PageUp,
                        "tab" => Key::Tab,
                        other => {
                            if let Some(arrow) = parse_arrow(other) {
                                Key::Arrow(arrow)
                            } else if let Some(f_key) = parse_f_key(other) {
                                Key::FKey(f_key)
                            } else {
                                Key::Char(single_key(other)?)
                            }
                        }
                    },
                    Some((k1, k2)) => match k1 {
                        "alt" => {
                            if let Some(arrow) = parse_arrow(k2) {
                                Key::AltArrow(arrow)
                            } else if let Some(f_key) = parse_f_key(k2) {
                                Key::AltF(f_key)
                            } else {
                                Key::AltChar(single_key(k2)?)
                            }
                        }
                        "ctrl" => {
                            if let Some(arrow) = parse_arrow(k2) {
                                Key::CtrlArrow(arrow)
                            } else if let Some(f_key) = parse_f_key(k2) {
                                Key::CtrlF(f_key)
                            } else {
                                Key::Ctrl(single_key(k2)?)
                            }
                        }
                        "shift" => {
                            if let Some(arrow) = parse_arrow(k2) {
                                Key::ShiftArrow(arrow)
                            } else if let Some(f_key) = parse_f_key(k2) {
                                Key::ShiftF(f_key)
                            } else {
                                return Err(E::invalid_value(Unexpected::Str(k2), &Self));
                            }
                        }
                        unexp => return Err(E::invalid_value(Unexpected::Str(unexp), &Self)),
                    },
                };

                if term_input::is_valid_key(key) {
                    Ok(MappedKey(key))
                } else {
                    Err(E::invalid_value(Unexpected::Str(v), &Self))
                }
            }
        }

        deserializer.deserialize_str(MappedKeyVisitor)
    }
}

fn parse_arrow(s: &str) -> Option<Arrow> {
    match s {
        "up" => Some(Arrow::Up),
        "down" => Some(Arrow::Down),
        "left" => Some(Arrow::Left),
        "right" => Some(Arrow::Right),
        _ => None,
    }
}

fn parse_f_key(s: &str) -> Option<FKey> {
    match s {
        "f1" => Some(FKey::F1),
        "f2" => Some(FKey::F2),
        "f3" => Some(FKey::F3),
        "f4" => Some(FKey::F4),
        "f5" => Some(FKey::F5),
        "f6" => Some(FKey::F6),
        "f7" => Some(FKey::F7),
        "f8" => Some(FKey::F8),
        "f9" => Some(FKey::F9),
        "f10" => Some(FKey::F10),
        "f11" => Some(FKey::F11),
        "f12" => Some(FKey::F12),
        _ => None,
    }
}

#[test]
fn deser_key() {
    let s = "alt_p";
    let key: MappedKey = serde_yaml::from_str(s).unwrap();
    assert_eq!(Key::AltChar('p'), key.0);
    let s = "alt_è";
    let key: MappedKey = serde_yaml::from_str(s).unwrap();
    assert_eq!(Key::AltChar('è'), key.0);
    let s = "alt__";
    let key: MappedKey = serde_yaml::from_str(s).unwrap();
    assert_eq!(Key::AltChar('_'), key.0);
    let s = "ctrl_/";
    let key: MappedKey = serde_yaml::from_str(s).unwrap();
    assert_eq!(Key::Ctrl('/'), key.0);
    let s = "ctrl_f2";
    let key: MappedKey = serde_yaml::from_str(s).unwrap();
    assert_eq!(Key::CtrlF(FKey::F2), key.0);
    let s = "alt_f2";
    let key: MappedKey = serde_yaml::from_str(s).unwrap();
    assert_eq!(Key::AltF(FKey::F2), key.0);
    let s = "shift_f2";
    let key: MappedKey = serde_yaml::from_str(s).unwrap();
    assert_eq!(Key::ShiftF(FKey::F2), key.0);
    let s = "shift_left";
    let key: MappedKey = serde_yaml::from_str(s).unwrap();
    assert_eq!(Key::ShiftArrow(Arrow::Left), key.0);
}

#[test]
fn deser_key_action_goto_tab() {
    let s = "tab_goto: 1";
    let a = serde_yaml::from_str::<KeyAction>(s).unwrap();
    assert_eq!(KeyAction::TabGoto('1'), a);
}

#[test]
fn deser_keymap() {
    let s = r#"
    ctrl_a: input_move_curs_start
    ctrl_e:
      tab_goto: 1
    a:
      input: b
    "#;
    let mut expect = KeyMap(HashMap::new());
    expect
        .0
        .insert(Key::Ctrl('a'), KeyAction::InputMoveCursStart);
    expect.0.insert(Key::Ctrl('e'), KeyAction::TabGoto('1'));
    expect.0.insert(Key::Char('a'), KeyAction::Input('b'));

    let key_map: KeyMap = serde_yaml::from_str(s).unwrap();
    assert_eq!(expect, key_map);
}
<|endoftext|>


<commit_before>use super::behaviors::MouseBehavior;
use crate::prelude::*;
use crate::shell::{Key, KeyEvent};
use core::f64::MAX;
use rust_decimal::prelude::*;

pub static ID_INPUT: &'static str = "numeric_box_input";
pub static ELEMENT_INPUT: &'static str = "numeric_box_input";
pub static ELEMENT_BTN: &'static str = "numeric_box_button";

pub enum InputAction {
    Inc,
    Dec,
    ChangeByKey(KeyEvent),
    ChangeByMouseScroll(Point),
    Focus,
}

#[derive(Default, AsAny)]
pub struct NumericBoxState {
    pub action: Option<InputAction>,
    pub input: Entity,
    pub min: Decimal,
    pub max: Decimal,
    pub step: Decimal,
    pub current_value: Decimal,
}

impl NumericBoxState {
    fn action(&mut self, action: InputAction) {
        self.action = Some(action);
    }

    fn change_val(&mut self, new_value: Decimal, ctx: &mut Context) {
        if self.current_value == self.min && new_value < self.min
        || self.current_value == self.max && new_value > self.max {
            return;
        }

        self.current_value = self.max(self.min(new_value));
        ctx.get_widget(self.input)
            .set::<String16>("text", String16::from(self.current_value.to_string()));

        ctx.push_event_strategy_by_entity(
            ChangedEvent(ctx.entity),
            ctx.entity,
            EventStrategy::Direct,
        );
    }

    fn min(&self, d: Decimal) -> Decimal {
        if d <= self.min {
            return self.min;
        } else {
            return d;
        }
    }

    fn max(&self, d: Decimal) -> Decimal {
       if d >= self.max {
           return self.max;
       } else {
           return d;
       }
    }

    fn request_focus(&self, ctx: &mut Context) {
        if !ctx.widget().get::<bool>("focused") {
            ctx.widget().set::<bool>("focused", true);
            ctx.push_event_by_window(FocusEvent::RequestFocus(ctx.entity));
        }
    }
}

fn default_or(key: &str, default_value: f64, ctx: &mut Context) -> Decimal {
    let property = ctx.widget().clone_or_default(key);

    match Decimal::from_f64(property) {
        Some(val) => val,
        None => Decimal::from_f64(default_value).unwrap(),
    }
}

impl State for NumericBoxState {
    fn init(&mut self, _: &mut Registry, ctx: &mut Context) {
        self.input = ctx.entity_of_child(ID_INPUT).expect(
            "NumericBoxState
        .init(): the child input could not be found!",
        );
        self.min = default_or("min", 0.0, ctx);
        self.max = default_or("max", MAX, ctx);
        self.step = default_or("step", 1.0, ctx);
        self.current_value = default_or("val", 0.0, ctx);

        let init_value = String16::from(self.current_value.to_string());
        ctx.get_widget(self.input)
            .set::<String16>("text", init_value);
    }

    // TODO: let the user type the value, or select it for cut, copy, paste operations
    fn update(&mut self, _: &mut Registry, ctx: &mut Context) {
        if let Some(action) = &self.action {
            match action {
                InputAction::Inc => {
                    self.change_val(self.current_value + self.step, ctx);
                }
                InputAction::Dec => {
                    self.change_val(self.current_value - self.step, ctx);
                }
                InputAction::ChangeByKey(key_event) => match key_event.key {
                    Key::Up | Key::NumpadAdd => {
                        self.change_val(self.current_value + self.step, ctx);
                    }
                    Key::Down | Key::NumpadSubtract => {
                        self.change_val(self.current_value - self.step, ctx);
                    }
                    Key::Enter => {
                        if *ctx.widget().get::<bool>("lost_focus_on_activation") {
                            ctx.push_event_by_window(FocusEvent::RemoveFocus(ctx.entity));
                        }

                        ctx.push_event_strategy_by_entity(
                            ActivateEvent(ctx.entity),
                            ctx.entity,
                            EventStrategy::Direct,
                        )
                    }
                    _ => {}
                },
                InputAction::ChangeByMouseScroll(delta) => {
                    if delta.y < 0.0 {
                        self.change_val(self.current_value - self.step, ctx);
                    } else {
                        self.change_val(self.current_value + self.step, ctx);
                    }
                }
                InputAction::Focus => {
                    self.request_focus(ctx);
                }
            }
            self.action = None;
        }
    }
}

widget!(
    /// `NumericBox` is used to let the user increase or decrease
    /// the value of the input by a given, fixed value called `step` until it reaches the upper or
    /// lower bounds.
    /// The widget can be controlled by clicking on the two control buttons, or the keybaord's
    /// Up and Down, Numpad+ and Numpad- keys, or the mouse scroll.
    /// Note: after the widget is initialized, changing the min, max or step properties has no effect.
    ///
    /// # Examples:
    /// Create a NumericBox with default values:
    /// ```rust
    /// NumericBox::new().build(ctx)
    /// ```
    ///
    /// Create a NumericBox with custom values:
    /// ```rust
    /// NumericBox::new().min(10.0).max(100.0).val(50.0).step(5.0).build(ctx)
    /// ```
    NumericBox<NumericBoxState>: ActivateHandler, ChangedHandler, KeyDownHandler {
        /// Sets or shares the background color property
        background: Brush,

        /// Sets or shares the border color property
        border_brush: Brush,

        /// Sets or shares the border width property
        border_width: Thickness,

        /// Sets or shares the border radius property
        border_radius: f64,

        /// Sets or shares the focused property
        focused: bool,

        /// Sets or shares the foreground color property
        foreground: Brush,

        /// Sets or shares the value that describes if the NumericBox should lost focus on activation (when enter pressed).
        lost_focus_on_activation: bool,

        /// Sets or shares the minimum allowed value property
        min: f64,

        /// Sets or shares the maximum allowed value property
        max: f64,

        /// Sets or shares the stepping value property
        step: f64,

        /// Sets or shares the current value property
        val: f64
    }
);

impl Template for NumericBox {
    fn template(self, id: Entity, ctx: &mut BuildContext) -> Self {
        self.name("NumericBox")
            .background("transparent")
            .foreground(colors::LINK_WATER_COLOR)
            .border_brush("#647b91")
            .border_width(1.0)
            .border_radius(3.0)
            .element("numeric_box")
            .focused(false)
            .height(32.0)
            .lost_focus_on_activation(true)
            .margin(4.0)
            .min(0.0)
            .max(200.0)
            .step(1.0)
            .val(0.0)
            .width(128.0)
            .child(
                MouseBehavior::new()
                    .on_mouse_down(move |states, _| {
                        states
                            .get_mut::<NumericBoxState>(id)
                            .action(InputAction::Focus);
                        true
                    })
                    .on_scroll(move |states, delta| {
                        states
                            .get_mut::<NumericBoxState>(id)
                            .action(InputAction::ChangeByMouseScroll(delta));
                        true
                    })
                    .build(ctx),
            )
            .child(
                Grid::new()
                    .columns(Columns::new().add("*").add(32.))
                    .rows(Rows::new().add(16.0).add(16.0))
                    .child(
                        TextBox::new()
                            .h_align("stretch")
                            .attach(Grid::column(0))
                            .attach(Grid::row_span(2))
                            .attach(Grid::row(0))
                            .border_brush(id)
                            .border_radius(id)
                            .border_width(id)
                            .element(ELEMENT_INPUT)
                            .enabled(false)
                            .id(ID_INPUT)
                            .max_width(96.)
                            .text("0")
                            .lost_focus_on_activation(id)
                            .build(ctx),
                    )
                    .child(
                        Button::new()
                            .attach(Grid::column(1))
                            .attach(Grid::row(0))
                            .border_brush("transparent")
                            .border_radius(0.0)
                            .border_width(0.0)
                            .min_width(30.0)
                            .height(30.0)
                            .class("single_content")
                            .element(ELEMENT_BTN)
                            .text("+")
                            .margin(1)
                            .on_click(move |states, _| {
                                states
                                    .get_mut::<NumericBoxState>(id)
                                    .action(InputAction::Inc);
                                true
                            })
                            .build(ctx),
                    )
                    .child(
                        Button::new()
                            .attach(Grid::column(1))
                            .attach(Grid::row(1))
                            .border_brush("transparent")
                            .border_radius(0.0)
                            .border_width(0.0)
                            .class("single_content")
                            .element(ELEMENT_BTN)
                            .min_width(30.0)
                            .height(30.0)
                            .padding(0.0)
                            .margin(1)
                            .text("-")
                            .on_click(move |states, _| {
                                states
                                    .get_mut::<NumericBoxState>(id)
                                    .action(InputAction::Dec);
                                true
                            })
                            .build(ctx),
                    )
                    .build(ctx),
            )
            .on_key_down(move |states, event| -> bool {
                states
                    .get_mut::<NumericBoxState>(id)
                    .action(InputAction::ChangeByKey(event));
                false
            })
    }

    fn render_object(&self) -> Box<dyn RenderObject> {
        Box::new(RectangleRenderObject)
    }
}
<commit_msg>Format<commit_after>use super::behaviors::MouseBehavior;
use crate::prelude::*;
use crate::shell::{Key, KeyEvent};
use core::f64::MAX;
use rust_decimal::prelude::*;

pub static ID_INPUT: &'static str = "numeric_box_input";
pub static ELEMENT_INPUT: &'static str = "numeric_box_input";
pub static ELEMENT_BTN: &'static str = "numeric_box_button";

pub enum InputAction {
    Inc,
    Dec,
    ChangeByKey(KeyEvent),
    ChangeByMouseScroll(Point),
    Focus,
}

#[derive(Default, AsAny)]
pub struct NumericBoxState {
    pub action: Option<InputAction>,
    pub input: Entity,
    pub min: Decimal,
    pub max: Decimal,
    pub step: Decimal,
    pub current_value: Decimal,
}

impl NumericBoxState {
    fn action(&mut self, action: InputAction) {
        self.action = Some(action);
    }

    fn change_val(&mut self, new_value: Decimal, ctx: &mut Context) {
        if self.current_value == self.min && new_value < self.min
            || self.current_value == self.max && new_value > self.max
        {
            return;
        }

        self.current_value = self.max(self.min(new_value));
        ctx.get_widget(self.input)
            .set::<String16>("text", String16::from(self.current_value.to_string()));

        ctx.push_event_strategy_by_entity(
            ChangedEvent(ctx.entity),
            ctx.entity,
            EventStrategy::Direct,
        );
    }

    fn min(&self, d: Decimal) -> Decimal {
        if d <= self.min {
            return self.min;
        } else {
            return d;
        }
    }

    fn max(&self, d: Decimal) -> Decimal {
        if d >= self.max {
            return self.max;
        } else {
            return d;
        }
    }

    fn request_focus(&self, ctx: &mut Context) {
        if !ctx.widget().get::<bool>("focused") {
            ctx.widget().set::<bool>("focused", true);
            ctx.push_event_by_window(FocusEvent::RequestFocus(ctx.entity));
        }
    }
}

fn default_or(key: &str, default_value: f64, ctx: &mut Context) -> Decimal {
    let property = ctx.widget().clone_or_default(key);

    match Decimal::from_f64(property) {
        Some(val) => val,
        None => Decimal::from_f64(default_value).unwrap(),
    }
}

impl State for NumericBoxState {
    fn init(&mut self, _: &mut Registry, ctx: &mut Context) {
        self.input = ctx.entity_of_child(ID_INPUT).expect(
            "NumericBoxState
        .init(): the child input could not be found!",
        );
        self.min = default_or("min", 0.0, ctx);
        self.max = default_or("max", MAX, ctx);
        self.step = default_or("step", 1.0, ctx);
        self.current_value = default_or("val", 0.0, ctx);

        let init_value = String16::from(self.current_value.to_string());
        ctx.get_widget(self.input)
            .set::<String16>("text", init_value);
    }

    // TODO: let the user type the value, or select it for cut, copy, paste operations
    fn update(&mut self, _: &mut Registry, ctx: &mut Context) {
        if let Some(action) = &self.action {
            match action {
                InputAction::Inc => {
                    self.change_val(self.current_value + self.step, ctx);
                }
                InputAction::Dec => {
                    self.change_val(self.current_value - self.step, ctx);
                }
                InputAction::ChangeByKey(key_event) => match key_event.key {
                    Key::Up | Key::NumpadAdd => {
                        self.change_val(self.current_value + self.step, ctx);
                    }
                    Key::Down | Key::NumpadSubtract => {
                        self.change_val(self.current_value - self.step, ctx);
                    }
                    Key::Enter => {
                        if *ctx.widget().get::<bool>("lost_focus_on_activation") {
                            ctx.push_event_by_window(FocusEvent::RemoveFocus(ctx.entity));
                        }

                        ctx.push_event_strategy_by_entity(
                            ActivateEvent(ctx.entity),
                            ctx.entity,
                            EventStrategy::Direct,
                        )
                    }
                    _ => {}
                },
                InputAction::ChangeByMouseScroll(delta) => {
                    if delta.y < 0.0 {
                        self.change_val(self.current_value - self.step, ctx);
                    } else {
                        self.change_val(self.current_value + self.step, ctx);
                    }
                }
                InputAction::Focus => {
                    self.request_focus(ctx);
                }
            }
            self.action = None;
        }
    }
}

widget!(
    /// `NumericBox` is used to let the user increase or decrease
    /// the value of the input by a given, fixed value called `step` until it reaches the upper or
    /// lower bounds.
    /// The widget can be controlled by clicking on the two control buttons, or the keybaord's
    /// Up and Down, Numpad+ and Numpad- keys, or the mouse scroll.
    /// Note: after the widget is initialized, changing the min, max or step properties has no effect.
    ///
    /// # Examples:
    /// Create a NumericBox with default values:
    /// ```rust
    /// NumericBox::new().build(ctx)
    /// ```
    ///
    /// Create a NumericBox with custom values:
    /// ```rust
    /// NumericBox::new().min(10.0).max(100.0).val(50.0).step(5.0).build(ctx)
    /// ```
    NumericBox<NumericBoxState>: ActivateHandler, ChangedHandler, KeyDownHandler {
        /// Sets or shares the background color property
        background: Brush,

        /// Sets or shares the border color property
        border_brush: Brush,

        /// Sets or shares the border width property
        border_width: Thickness,

        /// Sets or shares the border radius property
        border_radius: f64,

        /// Sets or shares the focused property
        focused: bool,

        /// Sets or shares the foreground color property
        foreground: Brush,

        /// Sets or shares the value that describes if the NumericBox should lost focus on activation (when enter pressed).
        lost_focus_on_activation: bool,

        /// Sets or shares the minimum allowed value property
        min: f64,

        /// Sets or shares the maximum allowed value property
        max: f64,

        /// Sets or shares the stepping value property
        step: f64,

        /// Sets or shares the current value property
        val: f64
    }
);

impl Template for NumericBox {
    fn template(self, id: Entity, ctx: &mut BuildContext) -> Self {
        self.name("NumericBox")
            .background("transparent")
            .foreground(colors::LINK_WATER_COLOR)
            .border_brush("#647b91")
            .border_width(1.0)
            .border_radius(3.0)
            .element("numeric_box")
            .focused(false)
            .height(32.0)
            .lost_focus_on_activation(true)
            .margin(4.0)
            .min(0.0)
            .max(200.0)
            .step(1.0)
            .val(0.0)
            .width(128.0)
            .child(
                MouseBehavior::new()
                    .on_mouse_down(move |states, _| {
                        states
                            .get_mut::<NumericBoxState>(id)
                            .action(InputAction::Focus);
                        true
                    })
                    .on_scroll(move |states, delta| {
                        states
                            .get_mut::<NumericBoxState>(id)
                            .action(InputAction::ChangeByMouseScroll(delta));
                        true
                    })
                    .build(ctx),
            )
            .child(
                Grid::new()
                    .columns(Columns::new().add("*").add(32.))
                    .rows(Rows::new().add(16.0).add(16.0))
                    .child(
                        TextBox::new()
                            .h_align("stretch")
                            .attach(Grid::column(0))
                            .attach(Grid::row_span(2))
                            .attach(Grid::row(0))
                            .border_brush(id)
                            .border_radius(id)
                            .border_width(id)
                            .element(ELEMENT_INPUT)
                            .enabled(false)
                            .id(ID_INPUT)
                            .max_width(96.)
                            .text("0")
                            .lost_focus_on_activation(id)
                            .build(ctx),
                    )
                    .child(
                        Button::new()
                            .attach(Grid::column(1))
                            .attach(Grid::row(0))
                            .border_brush("transparent")
                            .border_radius(0.0)
                            .border_width(0.0)
                            .min_width(30.0)
                            .height(30.0)
                            .class("single_content")
                            .element(ELEMENT_BTN)
                            .text("+")
                            .margin(1)
                            .on_click(move |states, _| {
                                states
                                    .get_mut::<NumericBoxState>(id)
                                    .action(InputAction::Inc);
                                true
                            })
                            .build(ctx),
                    )
                    .child(
                        Button::new()
                            .attach(Grid::column(1))
                            .attach(Grid::row(1))
                            .border_brush("transparent")
                            .border_radius(0.0)
                            .border_width(0.0)
                            .class("single_content")
                            .element(ELEMENT_BTN)
                            .min_width(30.0)
                            .height(30.0)
                            .padding(0.0)
                            .margin(1)
                            .text("-")
                            .on_click(move |states, _| {
                                states
                                    .get_mut::<NumericBoxState>(id)
                                    .action(InputAction::Dec);
                                true
                            })
                            .build(ctx),
                    )
                    .build(ctx),
            )
            .on_key_down(move |states, event| -> bool {
                states
                    .get_mut::<NumericBoxState>(id)
                    .action(InputAction::ChangeByKey(event));
                false
            })
    }

    fn render_object(&self) -> Box<dyn RenderObject> {
        Box::new(RectangleRenderObject)
    }
}
<|endoftext|>