<commit_before>use std::sync::{
    atomic::{AtomicBool, Ordering},
    mpsc, Arc,
};

#[cfg(not(target_os = "redox"))]
use std::thread;

use super::MouseState;
use crate::{
    event::{ButtonState, Key, KeyEvent, MouseButton, MouseEvent},
    render::RenderContext2D,
    window_adapter::WindowAdapter,
    WindowRequest,
};

#[cfg(not(target_os = "redox"))]
use sdl2::event;

use orbclient::Renderer;

#[cfg(not(target_os = "redox"))]
use raw_window_handle::HasRawWindowHandle;

use orbtk_utils::Point;

/// Represents a wrapper structure consumed by an orbclient window.
///
/// Events are handled and propagated to the window adapter. The
/// window adapter will update corresponding entities, that are marked
/// `dirty`. If needed, the render pipeline will process those
/// entities and update the render buffer. Results are forwarded to
/// the output device.
pub struct Window
where
    A: WindowAdapter,
{
    adapter: A,
    close: bool,
    has_clipboard_update: bool,
    mouse: MouseState,
    update: bool,
    redraw: Arc<AtomicBool>,
    render_context: RenderContext2D,
    request_receiver: Option<mpsc::Receiver<WindowRequest>>,
    #[cfg(not(target_os = "redox"))]
    _sdl2_sync_thread: Option<thread::JoinHandle<()>>,
    //window_state: WindowState,
    window: orbclient::Window,
}

// Internal sync method betwee OrbClient and sdl2 backend
#[cfg(not(target_os = "redox"))]
fn init_sync(
    window: &orbclient::Window,
    receiver: mpsc::Receiver<WindowRequest>,
) -> (mpsc::Receiver<WindowRequest>, thread::JoinHandle<()>) {
    let (internal_sender, internal_receiver) = mpsc::channel();

    let event_sender = window.event_sender();
    let id = window.id();

    let _sdl2_sync_thread = thread::spawn(move || loop {
        for request in receiver.iter() {
            let _ = internal_sender.send(request.clone());
            if request == WindowRequest::Redraw {
                let _ = event_sender.push_event(event::Event::Window {
                    win_event: event::WindowEvent::None,
                    window_id: id,
                    timestamp: 0,
                });
            }
        }
    });

    (internal_receiver, _sdl2_sync_thread)
}

impl Window
where
    A: WindowAdapter,
{
    #[cfg(target_os = "redox")]
    pub fn new(
        adapter: A,
        render_context: RenderContext2D,
        request_receiver: Option<mpsc::Receiver<WindowRequest>>,
        window: orbclient::Window,
    ) -> Self {
        Window {
            adapter,
            render_context,
            request_receiver,
            // window_state: WindowState::default(),
            mouse: MouseState::default(),
            update: true,
            redraw: Arc::new(AtomicBool::new(true)),
            close: false,
            has_clipboard_update: true,
            window,
        }
    }

    #[cfg(not(target_os = "redox"))]
    pub fn new(
        adapter: A,
        render_context: RenderContext2D,
        request_receiver: Option<mpsc::Receiver<WindowRequest>>,
        window: orbclient::Window,
    ) -> Self {
        let mut adapter = adapter;
        let redraw: Arc<AtomicBool> = Arc::new(AtomicBool::new(true));

        adapter.set_raw_window_handle(window.raw_window_handle());

        let (request_receiver, _sdl2_sync_thread) = {
            if let Some(receiver) = request_receiver {
                let (rec, sync) = init_sync(&window, receiver);
                (Some(rec), Some(sync))
            } else {
                (None, None)
            }
        };

        Window {
            adapter,
            close: false,
            has_clipboard_update: true,
            mouse: MouseState::default(),
            redraw,
            render_context,
            request_receiver,
            _sdl2_sync_thread,
            update: true,
            // window_state: WindowState::default(),
            window,
        }
    }

    #[cfg(not(target_os = "redox"))]
    fn has_clipboard_update(&self) -> bool {
        self.has_clipboard_update
    }

    // todo: workaround until clipboard update events available on orbital
    #[cfg(target_os = "redox")]
    fn has_clipboard_update(&self) -> bool {
        true
    }
}

#[cfg(not(target_os = "redox"))]
// TODO: Safety: Explain why we are allowed to consume a raw window handle
unsafe impl raw_window_handle::HasRawWindowHandle for Window
where
    A: WindowAdapter,
{
    fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
        self.window.raw_window_handle()
    }
}

impl Window
where
    A: WindowAdapter,
{
    /// Drain events and propagate the events to the adapter.
    pub fn drain_events(&mut self) {
        for event in self.window.events() {
            match event.to_option() {
                orbclient::EventOption::Key(event) => {
                    self.push_key_event(event);
                    self.update = true;
                }
                orbclient::EventOption::TextInput(event) => {
                    self.adapter.text_input(event.character.to_string());
                    self.update = true;
                }
                orbclient::EventOption::Mouse(event) => {
                    self.mouse.mouse_pos = (event.x as f32, event.y as f32);
                    self.adapter.mouse(event.x as f64, event.y as f64);
                    self.update = true;
                }
                orbclient::EventOption::MouseRelative(_) => {}
                orbclient::EventOption::ClipboardUpdate(_) => {
                    self.has_clipboard_update = true;
                }
                orbclient::EventOption::Button(event) => {
                    if event.left != self.mouse.button_left {
                        if event.left {
                            self.push_mouse_event(true, MouseButton::Left);
                        } else {
                            self.push_mouse_event(false, MouseButton::Left);
                        }
                        self.mouse.button_left = event.left;
                        self.update = true;
                    }

                    if event.middle != self.mouse.button_middle {
                        if event.middle {
                            self.push_mouse_event(true, MouseButton::Middle);
                        } else {
                            self.push_mouse_event(false, MouseButton::Middle);
                        }
                        self.mouse.button_middle = event.middle;
                        self.update = true;
                    }

                    if event.right != self.mouse.button_right {
                        if event.right {
                            self.push_mouse_event(true, MouseButton::Right);
                        } else {
                            self.push_mouse_event(false, MouseButton::Right);
                        }
                        self.mouse.button_right = event.right;
                        self.update = true;
                    }
                }
                orbclient::EventOption::Scroll(event) => {
                    self.adapter.scroll(event.x as f64, event.y as f64);
                    self.update = true;
                }
                orbclient::EventOption::Quit(_) => {
                    self.close = true;
                    self.update = true
                }
                orbclient::EventOption::Focus(_) => {}
                orbclient::EventOption::Move(_) => {}
                orbclient::EventOption::Resize(event) => {
                    self.adapter.resize(event.width as f64, event.height as f64);
                    self.render_context
                        .resize(event.width as f64, event.height as f64);
                    self.update = true;
                    self.redraw.store(true, Ordering::Relaxed);
                }
                orbclient::EventOption::Screen(_) => {}
                orbclient::EventOption::Clipboard(_) => {}
                orbclient::EventOption::Drop(event) => {
                    self.window.sync_path();
                    if let Some(text) = self.window.pop_drop_content() {
                        if event.kind == orbclient::DROP_FILE {
                            self.adapter.file_drop_event(text);
                        } else {
                            self.adapter.text_drop_event(text);
                        }
                    }
                }
                orbclient::EventOption::Unknown(_) => {}
                orbclient::EventOption::None => {}
                orbclient::EventOption::Hover(_) => {}
            }
        }
    }

    /// Check if the window is open.
    pub fn is_open(&self) -> bool {
        !self.close
    }

    fn push_key_event(&mut self, key_event: orbclient::KeyEvent) {
        let mut key = Key::from(key_event.character);
        let state = {
            if key_event.pressed {
                ButtonState::Down
            } else {
                ButtonState::Up
            }
        };

        let text = {
            if key_event.character != '\0'
                && key_event.character != '\n'
                && key_event.character != '\u{1b}'
            {
                key_event.character.to_string()
            } else {
                match key_event.scancode {
                    orbclient::K_ALT => key = Key::Alt,
                    orbclient::K_BKSP => key = Key::Backspace,
                    orbclient::K_CAPS => key = Key::CapsLock,
                    orbclient::K_CTRL => key = Key::Control,
                    orbclient::K_DEL => key = Key::Delete,
                    orbclient::K_DOWN => key = Key::Down,
                    orbclient::K_ENTER => key = Key::Enter,
                    orbclient::K_ESC => key = Key::Escape,
                    orbclient::K_HOME => {
                        key = Key::Home;
                    }
                    orbclient::K_LEFT => key = Key::Left,
                    orbclient::K_LEFT_SHIFT => key = Key::ShiftL,
                    orbclient::K_RIGHT => key = Key::Right,
                    orbclient::K_RIGHT_SHIFT => key = Key::ShiftR,
                    orbclient::K_TAB => key = Key::Tab,
                    orbclient::K_UP => key = Key::Up,
                    _ => key = Key::Unknown,
                };
                String::default()
            }
        };

        self.adapter.key_event(KeyEvent { state, key, text });
    }

    fn push_mouse_event(&mut self, pressed: bool, button: MouseButton) {
        let state = if pressed {
            ButtonState::Down
        } else {
            ButtonState::Up
        };

        self.adapter.mouse_event(MouseEvent {
            position: Point::new(self.mouse.mouse_pos.0 as f64, self.mouse.mouse_pos.1 as f64),
            button,
            state,
        });
    }

    /// Receives window request from the application and handles them.
    pub fn receive_requests(&mut self) {
        if let Some(request_receiver) = &self.request_receiver {
            for request in request_receiver.try_iter() {
                match request {
                    WindowRequest::Redraw => {
                        if !self.update && !self.redraw.load(Ordering::Relaxed) {
                            self.update = true;
                            self.redraw.store(true, Ordering::Relaxed)
                        }
                    }
                    WindowRequest::ChangeTitle(title) => {
                        self.window.set_title(title.as_str());
                        self.update = true;
                        self.redraw.store(true, Ordering::Relaxed)
                    }
                    WindowRequest::Close => {
                        self.close = true;
                    }
                }
            }
        }
    }

    /// Swaps the current frame buffer.
    pub fn render(&mut self) {
        if self.redraw.load(Ordering::Relaxed) {
            let bytes = self.render_context.data_u8_mut();
            let len = bytes.len() / std::mem::size_of::<orbclient::Color>();
            let color_data = unsafe {
                std::slice::from_raw_parts_mut(bytes.as_mut_ptr() as *mut orbclient::Color, len)
            };

            if color_data.len() == self.window.data().len() {
                self.window.data_mut().clone_from_slice(color_data);

                // CONSOLE.time_end("render");
                self.redraw.store(false, Ordering::Relaxed)
                //super::CONSOLE.time_end("complete");
            }
        }

        self.window.sync();
    }

    /// Triggers an update for the given window adapter.
    pub fn update(&mut self) {
        //super::CONSOLE.time("complete");
        if !self.update {
            return;
        }

        self.adapter.run(&mut self.render_context);
        self.update = false;
        self.redraw.store(true, Ordering::Relaxed)
    }
    /// Updates the clipboard.
    pub fn update_clipboard(&mut self) {
        let mut clipboard_value = if self.has_clipboard_update() {
            self.has_clipboard_update = false;
            Some(self.window.clipboard())
        } else {
            None
        };

        self.adapter.clipboard_update(&mut clipboard_value);

        if let Some(value) = clipboard_value {
            self.window.set_clipboard(value.as_str());
        }
    }
}
<commit_msg>orbtk_orbclient: document that channels are asynchronous<commit_after>use std::sync::{
    atomic::{AtomicBool, Ordering},
    mpsc, Arc,
};

#[cfg(not(target_os = "redox"))]
use std::thread;

use super::MouseState;
use crate::{
    event::{ButtonState, Key, KeyEvent, MouseButton, MouseEvent},
    render::RenderContext2D,
    window_adapter::WindowAdapter,
    WindowRequest,
};

#[cfg(not(target_os = "redox"))]
use sdl2::event;

use orbclient::Renderer;

#[cfg(not(target_os = "redox"))]
use raw_window_handle::HasRawWindowHandle;

use orbtk_utils::Point;

/// Represents a wrapper structure consumed by an orbclient window.
///
/// Events are handled and propagated to the window adapter. The
/// window adapter will update corresponding entities, that are marked
/// `dirty`. If needed, the render pipeline will process those
/// entities and update the render buffer. Results are forwarded to
/// the output device.
pub struct Window
where
    A: WindowAdapter,
{
    adapter: A,
    close: bool,
    has_clipboard_update: bool,
    mouse: MouseState,
    update: bool,
    redraw: Arc<AtomicBool>,
    render_context: RenderContext2D,
    request_receiver: Option<mpsc::Receiver<WindowRequest>>,
    #[cfg(not(target_os = "redox"))]
    _sdl2_sync_thread: Option<thread::JoinHandle<()>>,
    //window_state: WindowState,
    window: orbclient::Window,
}

// Internal sync method betwee OrbClient and sdl2 backend
#[cfg(not(target_os = "redox"))]
fn init_sync(
    window: &orbclient::Window,
    receiver: mpsc::Receiver<WindowRequest>,
) -> (mpsc::Receiver<WindowRequest>, thread::JoinHandle<()>) {
    // channel: sends will never block, they are asynchronous
    let (internal_sender, internal_receiver) = mpsc::channel();

    let event_sender = window.event_sender();
    let id = window.id();

    let _sdl2_sync_thread = thread::spawn(move || loop {
        for request in receiver.iter() {
            let _ = internal_sender.send(request.clone());
            if request == WindowRequest::Redraw {
                let _ = event_sender.push_event(event::Event::Window {
                    win_event: event::WindowEvent::None,
                    window_id: id,
                    timestamp: 0,
                });
            }
        }
    });

    (internal_receiver, _sdl2_sync_thread)
}

impl Window
where
    A: WindowAdapter,
{
    #[cfg(target_os = "redox")]
    pub fn new(
        adapter: A,
        render_context: RenderContext2D,
        request_receiver: Option<mpsc::Receiver<WindowRequest>>,
        window: orbclient::Window,
    ) -> Self {
        Window {
            adapter,
            render_context,
            request_receiver,
            // window_state: WindowState::default(),
            mouse: MouseState::default(),
            update: true,
            redraw: Arc::new(AtomicBool::new(true)),
            close: false,
            has_clipboard_update: true,
            window,
        }
    }

    #[cfg(not(target_os = "redox"))]
    pub fn new(
        adapter: A,
        render_context: RenderContext2D,
        request_receiver: Option<mpsc::Receiver<WindowRequest>>,
        window: orbclient::Window,
    ) -> Self {
        let mut adapter = adapter;
        let redraw: Arc<AtomicBool> = Arc::new(AtomicBool::new(true));

        adapter.set_raw_window_handle(window.raw_window_handle());

        let (request_receiver, _sdl2_sync_thread) = {
            if let Some(receiver) = request_receiver {
                let (rec, sync) = init_sync(&window, receiver);
                (Some(rec), Some(sync))
            } else {
                (None, None)
            }
        };

        Window {
            adapter,
            close: false,
            has_clipboard_update: true,
            mouse: MouseState::default(),
            redraw,
            render_context,
            request_receiver,
            _sdl2_sync_thread,
            update: true,
            // window_state: WindowState::default(),
            window,
        }
    }

    #[cfg(not(target_os = "redox"))]
    fn has_clipboard_update(&self) -> bool {
        self.has_clipboard_update
    }

    // todo: workaround until clipboard update events available on orbital
    #[cfg(target_os = "redox")]
    fn has_clipboard_update(&self) -> bool {
        true
    }
}

#[cfg(not(target_os = "redox"))]
// TODO: Safety: Explain why we are allowed to consume a raw window handle
unsafe impl raw_window_handle::HasRawWindowHandle for Window
where
    A: WindowAdapter,
{
    fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
        self.window.raw_window_handle()
    }
}

impl Window
where
    A: WindowAdapter,
{
    /// Drain events and propagate the events to the adapter.
    pub fn drain_events(&mut self) {
        for event in self.window.events() {
            match event.to_option() {
                orbclient::EventOption::Key(event) => {
                    self.push_key_event(event);
                    self.update = true;
                }
                orbclient::EventOption::TextInput(event) => {
                    self.adapter.text_input(event.character.to_string());
                    self.update = true;
                }
                orbclient::EventOption::Mouse(event) => {
                    self.mouse.mouse_pos = (event.x as f32, event.y as f32);
                    self.adapter.mouse(event.x as f64, event.y as f64);
                    self.update = true;
                }
                orbclient::EventOption::MouseRelative(_) => {}
                orbclient::EventOption::ClipboardUpdate(_) => {
                    self.has_clipboard_update = true;
                }
                orbclient::EventOption::Button(event) => {
                    if event.left != self.mouse.button_left {
                        if event.left {
                            self.push_mouse_event(true, MouseButton::Left);
                        } else {
                            self.push_mouse_event(false, MouseButton::Left);
                        }
                        self.mouse.button_left = event.left;
                        self.update = true;
                    }

                    if event.middle != self.mouse.button_middle {
                        if event.middle {
                            self.push_mouse_event(true, MouseButton::Middle);
                        } else {
                            self.push_mouse_event(false, MouseButton::Middle);
                        }
                        self.mouse.button_middle = event.middle;
                        self.update = true;
                    }

                    if event.right != self.mouse.button_right {
                        if event.right {
                            self.push_mouse_event(true, MouseButton::Right);
                        } else {
                            self.push_mouse_event(false, MouseButton::Right);
                        }
                        self.mouse.button_right = event.right;
                        self.update = true;
                    }
                }
                orbclient::EventOption::Scroll(event) => {
                    self.adapter.scroll(event.x as f64, event.y as f64);
                    self.update = true;
                }
                orbclient::EventOption::Quit(_) => {
                    self.close = true;
                    self.update = true
                }
                orbclient::EventOption::Focus(_) => {}
                orbclient::EventOption::Move(_) => {}
                orbclient::EventOption::Resize(event) => {
                    self.adapter.resize(event.width as f64, event.height as f64);
                    self.render_context
                        .resize(event.width as f64, event.height as f64);
                    self.update = true;
                    self.redraw.store(true, Ordering::Relaxed);
                }
                orbclient::EventOption::Screen(_) => {}
                orbclient::EventOption::Clipboard(_) => {}
                orbclient::EventOption::Drop(event) => {
                    self.window.sync_path();
                    if let Some(text) = self.window.pop_drop_content() {
                        if event.kind == orbclient::DROP_FILE {
                            self.adapter.file_drop_event(text);
                        } else {
                            self.adapter.text_drop_event(text);
                        }
                    }
                }
                orbclient::EventOption::Unknown(_) => {}
                orbclient::EventOption::None => {}
                orbclient::EventOption::Hover(_) => {}
            }
        }
    }

    /// Check if the window is open.
    pub fn is_open(&self) -> bool {
        !self.close
    }

    fn push_key_event(&mut self, key_event: orbclient::KeyEvent) {
        let mut key = Key::from(key_event.character);
        let state = {
            if key_event.pressed {
                ButtonState::Down
            } else {
                ButtonState::Up
            }
        };

        let text = {
            if key_event.character != '\0'
                && key_event.character != '\n'
                && key_event.character != '\u{1b}'
            {
                key_event.character.to_string()
            } else {
                match key_event.scancode {
                    orbclient::K_ALT => key = Key::Alt,
                    orbclient::K_BKSP => key = Key::Backspace,
                    orbclient::K_CAPS => key = Key::CapsLock,
                    orbclient::K_CTRL => key = Key::Control,
                    orbclient::K_DEL => key = Key::Delete,
                    orbclient::K_DOWN => key = Key::Down,
                    orbclient::K_ENTER => key = Key::Enter,
                    orbclient::K_ESC => key = Key::Escape,
                    orbclient::K_HOME => {
                        key = Key::Home;
                    }
                    orbclient::K_LEFT => key = Key::Left,
                    orbclient::K_LEFT_SHIFT => key = Key::ShiftL,
                    orbclient::K_RIGHT => key = Key::Right,
                    orbclient::K_RIGHT_SHIFT => key = Key::ShiftR,
                    orbclient::K_TAB => key = Key::Tab,
                    orbclient::K_UP => key = Key::Up,
                    _ => key = Key::Unknown,
                };
                String::default()
            }
        };

        self.adapter.key_event(KeyEvent { state, key, text });
    }

    fn push_mouse_event(&mut self, pressed: bool, button: MouseButton) {
        let state = if pressed {
            ButtonState::Down
        } else {
            ButtonState::Up
        };

        self.adapter.mouse_event(MouseEvent {
            position: Point::new(self.mouse.mouse_pos.0 as f64, self.mouse.mouse_pos.1 as f64),
            button,
            state,
        });
    }

    /// Receives window request from the application and handles them.
    pub fn receive_requests(&mut self) {
        if let Some(request_receiver) = &self.request_receiver {
            for request in request_receiver.try_iter() {
                match request {
                    WindowRequest::Redraw => {
                        if !self.update && !self.redraw.load(Ordering::Relaxed) {
                            self.update = true;
                            self.redraw.store(true, Ordering::Relaxed)
                        }
                    }
                    WindowRequest::ChangeTitle(title) => {
                        self.window.set_title(title.as_str());
                        self.update = true;
                        self.redraw.store(true, Ordering::Relaxed)
                    }
                    WindowRequest::Close => {
                        self.close = true;
                    }
                }
            }
        }
    }

    /// Swaps the current frame buffer.
    pub fn render(&mut self) {
        if self.redraw.load(Ordering::Relaxed) {
            let bytes = self.render_context.data_u8_mut();
            let len = bytes.len() / std::mem::size_of::<orbclient::Color>();
            let color_data = unsafe {
                std::slice::from_raw_parts_mut(bytes.as_mut_ptr() as *mut orbclient::Color, len)
            };

            if color_data.len() == self.window.data().len() {
                self.window.data_mut().clone_from_slice(color_data);

                // CONSOLE.time_end("render");
                self.redraw.store(false, Ordering::Relaxed)
                //super::CONSOLE.time_end("complete");
            }
        }

        self.window.sync();
    }

    /// Triggers an update for the given window adapter.
    pub fn update(&mut self) {
        //super::CONSOLE.time("complete");
        if !self.update {
            return;
        }

        self.adapter.run(&mut self.render_context);
        self.update = false;
        self.redraw.store(true, Ordering::Relaxed)
    }
    /// Updates the clipboard.
    pub fn update_clipboard(&mut self) {
        let mut clipboard_value = if self.has_clipboard_update() {
            self.has_clipboard_update = false;
            Some(self.window.clipboard())
        } else {
            None
        };

        self.adapter.clipboard_update(&mut clipboard_value);

        if let Some(value) = clipboard_value {
            self.window.set_clipboard(value.as_str());
        }
    }
}
<|endoftext|>


<commit_before>use term_input::Key;
use termbox_simple::Termbox;

use std::convert::From;

use time::{self, Tm};

use crate::config::Colors;
use crate::exit_dialogue::ExitDialogue;
use crate::input_area::InputArea;
use crate::msg_area::line::{SchemeStyle, SegStyle};
use crate::msg_area::MsgArea;
use crate::trie::Trie;
use crate::widget::WidgetRet;

/// A messaging screen is just a text field to type messages and msg area to
/// show incoming/sent messages.
pub(crate) struct MessagingUI {
    /// Incoming and sent messages appear
    msg_area: MsgArea,

    // exit_dialogue handles input when available.
    // two fields (instead of an enum etc.) to avoid borrowchk problems
    input_field: InputArea,
    exit_dialogue: Option<ExitDialogue>,

    width: i32,
    height: i32,

    // Option to disable status messages ( join/part )
    show_status: bool,

    // All nicks in the channel. Need to keep this up-to-date to be able to
    // properly highlight mentions.
    nicks: Trie,

    last_activity_line: Option<ActivityLine>,
    last_activity_ts: Option<Timestamp>,
}

/// Like `time::Tm`, but we only care about hour and minute parts.
#[derive(PartialEq, Eq, Clone, Copy)]
pub(crate) struct Timestamp {
    hour: i32,
    min: i32,
}

impl Timestamp {
    fn stamp(self, msg_area: &mut MsgArea) {
        msg_area.set_style(SegStyle::SchemeStyle(SchemeStyle::Timestamp));
        msg_area.add_text(&format!("{:02}:{:02} ", self.hour, self.min));
    }
}

impl From<Tm> for Timestamp {
    fn from(tm: Tm) -> Timestamp {
        Timestamp {
            hour: tm.tm_hour,
            min: tm.tm_min,
        }
    }
}

/// An activity line is just a line that we update on joins / leaves /
/// disconnects. We group activities that happen in the same minute to avoid
/// redundantly showing lines.
struct ActivityLine {
    ts: Timestamp,
    line_idx: usize,
}

impl MessagingUI {
    pub(crate) fn new(width: i32, height: i32, status: bool, scrollback: usize) -> MessagingUI {
        MessagingUI {
            msg_area: MsgArea::new(width, height - 1, scrollback),
            input_field: InputArea::new(width, get_input_field_max_height(height)),
            exit_dialogue: None,
            width,
            height,
            show_status: status,
            nicks: Trie::new(),
            last_activity_line: None,
            last_activity_ts: None,
        }
    }

    pub(crate) fn set_nick(&mut self, nick: String) {
        let nick_color = self.get_nick_color(&nick);
        self.input_field.set_nick(nick, nick_color);
        // update text field size
        let w = self.width;
        let h = self.height;
        self.resize(w, h);
    }

    pub(crate) fn get_nick(&self) -> Option<String> {
        self.input_field.get_nick()
    }

    pub(crate) fn draw(&mut self, tb: &mut Termbox, colors: &Colors, pos_x: i32, pos_y: i32) {
        match &self.exit_dialogue {
            Some(exit_dialogue) => {
                exit_dialogue.draw(tb, colors, pos_x, self.height - 1);
            }
            None => {
                // Draw InputArea first because it can trigger a resize of MsgArea
                self.input_field
                    .draw(tb, colors, pos_x, pos_y, self.height, &mut self.msg_area);
            }
        }
        self.msg_area.draw(tb, colors, pos_x, pos_y);
    }

    pub(crate) fn keypressed(&mut self, key: Key) -> WidgetRet {
        match key {
            Key::Ctrl('c') => {
                self.toggle_exit_dialogue();
                WidgetRet::KeyHandled
            }

            Key::Ctrl('u') | Key::PageUp => {
                self.msg_area.page_up();
                WidgetRet::KeyHandled
            }

            Key::Ctrl('d') | Key::PageDown => {
                self.msg_area.page_down();
                WidgetRet::KeyHandled
            }

            Key::ShiftUp => {
                self.msg_area.scroll_up();
                WidgetRet::KeyHandled
            }

            Key::ShiftDown => {
                self.msg_area.scroll_down();
                WidgetRet::KeyHandled
            }

            Key::Tab => {
                if self.exit_dialogue.is_none() {
                    self.input_field.autocomplete(&self.nicks);
                }
                WidgetRet::KeyHandled
            }

            Key::Home => {
                self.msg_area.scroll_top();
                WidgetRet::KeyHandled
            }

            Key::End => {
                self.msg_area.scroll_bottom();
                WidgetRet::KeyHandled
            }

            key => {
                let ret = {
                    if let Some(exit_dialogue) = self.exit_dialogue.as_ref() {
                        exit_dialogue.keypressed(key)
                    } else {
                        self.input_field.keypressed(key)
                    }
                };

                if let WidgetRet::Remove = ret {
                    self.exit_dialogue = None;
                    WidgetRet::KeyHandled
                } else {
                    ret
                }
            }
        }
    }

    pub(crate) fn resize(&mut self, width: i32, height: i32) {
        self.width = width;
        self.height = height;

        self.input_field
            .resize(width, get_input_field_max_height(height));
        // msg_area should resize based on input_field's rendered height
        let msg_area_height = height - self.input_field.get_height(width);
        self.msg_area.resize(width, msg_area_height);

        // We don't show the nick in exit dialogue, so it has the full width
        for exit_dialogue in &mut self.exit_dialogue {
            exit_dialogue.resize(width);
        }
    }

    /// Get contents of the input field and cursor location and clear it.
    pub(crate) fn flush_input_field(&mut self) -> (String, i32) {
        self.input_field.flush()
    }

    /// Add a line to input field history.
    pub(crate) fn add_input_field_history(&mut self, str: &str) {
        self.input_field.add_history(str)
    }

    /// Set input field contents.
    pub(crate) fn set_input_field(&mut self, str: &str) {
        self.input_field.set(str)
    }

    fn toggle_exit_dialogue(&mut self) {
        let exit_dialogue = ::std::mem::replace(&mut self.exit_dialogue, None);
        if exit_dialogue.is_none() {
            // We don't show the nick in exit dialogue, so it has the full width
            self.exit_dialogue = Some(ExitDialogue::new(self.width));
        }
    }
}

/// Calculation for input field's maximum height
fn get_input_field_max_height(window_height: i32) -> i32 {
    window_height / 2
}

////////////////////////////////////////////////////////////////////////////////
// Adding new messages

impl MessagingUI {
    fn add_timestamp(&mut self, ts: Timestamp) {
        if let Some(ts_) = self.last_activity_ts {
            if ts_ != ts {
                ts.stamp(&mut self.msg_area);
            }
        } else {
            ts.stamp(&mut self.msg_area);
        }
        self.last_activity_ts = Some(ts);
    }

    pub(crate) fn show_topic(&mut self, topic: &str, ts: Timestamp) {
        self.add_timestamp(ts);

        self.msg_area
            .set_style(SegStyle::SchemeStyle(SchemeStyle::Topic));
        self.msg_area.add_text(topic);

        self.msg_area.flush_line();
    }

    pub(crate) fn add_client_err_msg(&mut self, msg: &str) {
        self.reset_activity_line();

        self.msg_area
            .set_style(SegStyle::SchemeStyle(SchemeStyle::ErrMsg));
        self.msg_area.add_text(msg);
        self.msg_area.flush_line();
    }

    pub(crate) fn add_client_notify_msg(&mut self, msg: &str) {
        self.reset_activity_line();

        self.msg_area
            .set_style(SegStyle::SchemeStyle(SchemeStyle::Faded));
        self.msg_area.add_text(msg);
        self.msg_area.flush_line();
        self.reset_activity_line();
    }

    pub(crate) fn add_client_msg(&mut self, msg: &str) {
        self.reset_activity_line();

        self.msg_area
            .set_style(SegStyle::SchemeStyle(SchemeStyle::UserMsg));
        self.msg_area.add_text(msg);
        self.msg_area.flush_line();
        self.reset_activity_line();
    }

    pub(crate) fn add_privmsg(
        &mut self,
        sender: &str,
        msg: &str,
        ts: Timestamp,
        highlight: bool,
        is_action: bool,
    ) {
        self.reset_activity_line();
        self.add_timestamp(ts);

        if is_action {
            self.msg_area
                .set_style(SegStyle::SchemeStyle(SchemeStyle::UserMsg));
            self.msg_area.add_text("** ");
        }

        {
            let nick_color = self.get_nick_color(sender);
            let style = SegStyle::Index(nick_color);
            self.msg_area.set_style(style);
            self.msg_area.add_text(sender);
        }

        self.msg_area
            .set_style(SegStyle::SchemeStyle(SchemeStyle::UserMsg));

        if !is_action {
            self.msg_area.add_char(':');
        }
        self.msg_area.add_char(' ');

        if highlight {
            self.msg_area
                .set_style(SegStyle::SchemeStyle(SchemeStyle::Highlight));
        }

        self.msg_area.add_text(msg);
        self.msg_area.flush_line();
    }

    pub(crate) fn add_msg(&mut self, msg: &str, ts: Timestamp) {
        self.reset_activity_line();

        self.add_timestamp(ts);
        self.msg_area
            .set_style(SegStyle::SchemeStyle(SchemeStyle::UserMsg));
        self.msg_area.add_text(msg);
        self.msg_area.flush_line();
    }

    pub(crate) fn add_err_msg(&mut self, msg: &str, ts: Timestamp) {
        self.reset_activity_line();

        self.add_timestamp(ts);
        self.msg_area
            .set_style(SegStyle::SchemeStyle(SchemeStyle::ErrMsg));
        self.msg_area.add_text(msg);
        self.msg_area.flush_line();
    }

    pub(crate) fn clear(&mut self) {
        self.msg_area.clear();
    }

    fn get_nick_color(&self, sender: &str) -> usize {
        // Anything works as long as it's fast
        let mut hash: usize = 5381;
        for c in sender.chars() {
            hash = hash.wrapping_mul(33).wrapping_add(c as usize);
        }
        hash
    }
}

////////////////////////////////////////////////////////////////////////////////
// Keeping nick list up-to-date

impl MessagingUI {
    pub(crate) fn clear_nicks(&mut self) {
        self.nicks.clear();
    }

    pub(crate) fn join(&mut self, nick: &str, ts: Option<Timestamp>) {
        if self.show_status && !self.nicks.contains(nick) {
            if let Some(ts) = ts {
                let line_idx = self.get_activity_line_idx(ts);
                self.msg_area.modify_line(line_idx, |line| {
                    line.set_style(SegStyle::SchemeStyle(SchemeStyle::Join));
                    line.add_char('+');
                    line.set_style(SegStyle::SchemeStyle(SchemeStyle::Faded));
                    line.add_text(nick);
                });
            }
        }

        self.nicks.insert(nick);
    }

    pub(crate) fn part(&mut self, nick: &str, ts: Option<Timestamp>) {
        self.nicks.remove(nick);

        if self.show_status {
            if let Some(ts) = ts {
                let line_idx = self.get_activity_line_idx(ts);
                self.msg_area.modify_line(line_idx, |line| {
                    line.set_style(SegStyle::SchemeStyle(SchemeStyle::Part));
                    line.add_char('-');
                    line.set_style(SegStyle::SchemeStyle(SchemeStyle::Faded));
                    line.add_text(nick);
                });
            }
        }
    }

    /// `state` == `None` means toggle
    /// `state` == `Some(state)` means set it to `state`
    pub(crate) fn set_or_toggle_ignore(&mut self, state: Option<bool>) {
        self.show_status = state.unwrap_or(!self.show_status);
        if self.show_status {
            self.add_client_notify_msg("Ignore disabled");
        } else {
            self.add_client_notify_msg("Ignore enabled");
        }
    }

    pub(crate) fn get_ignore_state(&self) -> bool {
        self.show_status
    }

    pub(crate) fn nick(&mut self, old_nick: &str, new_nick: &str, ts: Timestamp) {
        self.nicks.remove(old_nick);
        self.nicks.insert(new_nick);

        let line_idx = self.get_activity_line_idx(ts);
        self.msg_area.modify_line(line_idx, |line| {
            line.set_style(SegStyle::SchemeStyle(SchemeStyle::Faded));
            line.add_text(old_nick);
            line.set_style(SegStyle::SchemeStyle(SchemeStyle::Nick));
            line.add_char('>');
            line.set_style(SegStyle::SchemeStyle(SchemeStyle::Faded));
            line.add_text(new_nick);
        });
    }

    fn reset_activity_line(&mut self) {
        self.last_activity_line = None;
    }

    fn get_activity_line_idx(&mut self, ts: Timestamp) -> usize {
        match self.last_activity_line {
            Some(ref l) if l.ts == ts => {
                let line_idx = l.line_idx;
                // FIXME: It's a bit hacky to add a space in this function which from the name
                // looks like a getter.
                // The idea is that we want to add a space *before* adding new stuff, not *after*,
                // to avoid adding redundant spaces. The test `small_screen_1` breaks if we don't
                // get this right.
                self.msg_area
                    .modify_line(line_idx, |line| line.add_char(' '));
                line_idx
            }
            _ => {
                self.add_timestamp(ts);
                let line_idx = self.msg_area.flush_line();
                self.last_activity_line = Some(ActivityLine { ts, line_idx });
                line_idx
            }
        }
    }
}
<commit_msg>libtiny_tui: Add users to tab-completion list on PRIVMSG<commit_after>use term_input::Key;
use termbox_simple::Termbox;

use std::convert::From;

use time::{self, Tm};

use crate::config::Colors;
use crate::exit_dialogue::ExitDialogue;
use crate::input_area::InputArea;
use crate::msg_area::line::{SchemeStyle, SegStyle};
use crate::msg_area::MsgArea;
use crate::trie::Trie;
use crate::widget::WidgetRet;

/// A messaging screen is just a text field to type messages and msg area to
/// show incoming/sent messages.
pub(crate) struct MessagingUI {
    /// Incoming and sent messages appear
    msg_area: MsgArea,

    // exit_dialogue handles input when available.
    // two fields (instead of an enum etc.) to avoid borrowchk problems
    input_field: InputArea,
    exit_dialogue: Option<ExitDialogue>,

    width: i32,
    height: i32,

    // Option to disable status messages ( join/part )
    show_status: bool,

    // All nicks in the channel. Need to keep this up-to-date to be able to
    // properly highlight mentions.
    nicks: Trie,

    last_activity_line: Option<ActivityLine>,
    last_activity_ts: Option<Timestamp>,
}

/// Like `time::Tm`, but we only care about hour and minute parts.
#[derive(PartialEq, Eq, Clone, Copy)]
pub(crate) struct Timestamp {
    hour: i32,
    min: i32,
}

impl Timestamp {
    fn stamp(self, msg_area: &mut MsgArea) {
        msg_area.set_style(SegStyle::SchemeStyle(SchemeStyle::Timestamp));
        msg_area.add_text(&format!("{:02}:{:02} ", self.hour, self.min));
    }
}

impl From<Tm> for Timestamp {
    fn from(tm: Tm) -> Timestamp {
        Timestamp {
            hour: tm.tm_hour,
            min: tm.tm_min,
        }
    }
}

/// An activity line is just a line that we update on joins / leaves /
/// disconnects. We group activities that happen in the same minute to avoid
/// redundantly showing lines.
struct ActivityLine {
    ts: Timestamp,
    line_idx: usize,
}

impl MessagingUI {
    pub(crate) fn new(width: i32, height: i32, status: bool, scrollback: usize) -> MessagingUI {
        MessagingUI {
            msg_area: MsgArea::new(width, height - 1, scrollback),
            input_field: InputArea::new(width, get_input_field_max_height(height)),
            exit_dialogue: None,
            width,
            height,
            show_status: status,
            nicks: Trie::new(),
            last_activity_line: None,
            last_activity_ts: None,
        }
    }

    pub(crate) fn set_nick(&mut self, nick: String) {
        let nick_color = self.get_nick_color(&nick);
        self.input_field.set_nick(nick, nick_color);
        // update text field size
        let w = self.width;
        let h = self.height;
        self.resize(w, h);
    }

    pub(crate) fn get_nick(&self) -> Option<String> {
        self.input_field.get_nick()
    }

    pub(crate) fn draw(&mut self, tb: &mut Termbox, colors: &Colors, pos_x: i32, pos_y: i32) {
        match &self.exit_dialogue {
            Some(exit_dialogue) => {
                exit_dialogue.draw(tb, colors, pos_x, self.height - 1);
            }
            None => {
                // Draw InputArea first because it can trigger a resize of MsgArea
                self.input_field
                    .draw(tb, colors, pos_x, pos_y, self.height, &mut self.msg_area);
            }
        }
        self.msg_area.draw(tb, colors, pos_x, pos_y);
    }

    pub(crate) fn keypressed(&mut self, key: Key) -> WidgetRet {
        match key {
            Key::Ctrl('c') => {
                self.toggle_exit_dialogue();
                WidgetRet::KeyHandled
            }

            Key::Ctrl('u') | Key::PageUp => {
                self.msg_area.page_up();
                WidgetRet::KeyHandled
            }

            Key::Ctrl('d') | Key::PageDown => {
                self.msg_area.page_down();
                WidgetRet::KeyHandled
            }

            Key::ShiftUp => {
                self.msg_area.scroll_up();
                WidgetRet::KeyHandled
            }

            Key::ShiftDown => {
                self.msg_area.scroll_down();
                WidgetRet::KeyHandled
            }

            Key::Tab => {
                if self.exit_dialogue.is_none() {
                    self.input_field.autocomplete(&self.nicks);
                }
                WidgetRet::KeyHandled
            }

            Key::Home => {
                self.msg_area.scroll_top();
                WidgetRet::KeyHandled
            }

            Key::End => {
                self.msg_area.scroll_bottom();
                WidgetRet::KeyHandled
            }

            key => {
                let ret = {
                    if let Some(exit_dialogue) = self.exit_dialogue.as_ref() {
                        exit_dialogue.keypressed(key)
                    } else {
                        self.input_field.keypressed(key)
                    }
                };

                if let WidgetRet::Remove = ret {
                    self.exit_dialogue = None;
                    WidgetRet::KeyHandled
                } else {
                    ret
                }
            }
        }
    }

    pub(crate) fn resize(&mut self, width: i32, height: i32) {
        self.width = width;
        self.height = height;

        self.input_field
            .resize(width, get_input_field_max_height(height));
        // msg_area should resize based on input_field's rendered height
        let msg_area_height = height - self.input_field.get_height(width);
        self.msg_area.resize(width, msg_area_height);

        // We don't show the nick in exit dialogue, so it has the full width
        for exit_dialogue in &mut self.exit_dialogue {
            exit_dialogue.resize(width);
        }
    }

    /// Get contents of the input field and cursor location and clear it.
    pub(crate) fn flush_input_field(&mut self) -> (String, i32) {
        self.input_field.flush()
    }

    /// Add a line to input field history.
    pub(crate) fn add_input_field_history(&mut self, str: &str) {
        self.input_field.add_history(str)
    }

    /// Set input field contents.
    pub(crate) fn set_input_field(&mut self, str: &str) {
        self.input_field.set(str)
    }

    fn toggle_exit_dialogue(&mut self) {
        let exit_dialogue = ::std::mem::replace(&mut self.exit_dialogue, None);
        if exit_dialogue.is_none() {
            // We don't show the nick in exit dialogue, so it has the full width
            self.exit_dialogue = Some(ExitDialogue::new(self.width));
        }
    }
}

/// Calculation for input field's maximum height
fn get_input_field_max_height(window_height: i32) -> i32 {
    window_height / 2
}

////////////////////////////////////////////////////////////////////////////////
// Adding new messages

impl MessagingUI {
    fn add_timestamp(&mut self, ts: Timestamp) {
        if let Some(ts_) = self.last_activity_ts {
            if ts_ != ts {
                ts.stamp(&mut self.msg_area);
            }
        } else {
            ts.stamp(&mut self.msg_area);
        }
        self.last_activity_ts = Some(ts);
    }

    pub(crate) fn show_topic(&mut self, topic: &str, ts: Timestamp) {
        self.add_timestamp(ts);

        self.msg_area
            .set_style(SegStyle::SchemeStyle(SchemeStyle::Topic));
        self.msg_area.add_text(topic);

        self.msg_area.flush_line();
    }

    pub(crate) fn add_client_err_msg(&mut self, msg: &str) {
        self.reset_activity_line();

        self.msg_area
            .set_style(SegStyle::SchemeStyle(SchemeStyle::ErrMsg));
        self.msg_area.add_text(msg);
        self.msg_area.flush_line();
    }

    pub(crate) fn add_client_notify_msg(&mut self, msg: &str) {
        self.reset_activity_line();

        self.msg_area
            .set_style(SegStyle::SchemeStyle(SchemeStyle::Faded));
        self.msg_area.add_text(msg);
        self.msg_area.flush_line();
        self.reset_activity_line();
    }

    pub(crate) fn add_client_msg(&mut self, msg: &str) {
        self.reset_activity_line();

        self.msg_area
            .set_style(SegStyle::SchemeStyle(SchemeStyle::UserMsg));
        self.msg_area.add_text(msg);
        self.msg_area.flush_line();
        self.reset_activity_line();
    }

    pub(crate) fn add_privmsg(
        &mut self,
        sender: &str,
        msg: &str,
        ts: Timestamp,
        highlight: bool,
        is_action: bool,
    ) {
        // HACK: Some servers (bridges) don't send RPL_NAMREPLY and JOIN/PART messages but we still
        // want to support tab completion on those servers, so when we see a message from someone
        // we add the user to the nick list so that tab completion will complete their nick. See
        // #253 for details.
        self.nicks.insert(sender);

        self.reset_activity_line();
        self.add_timestamp(ts);

        if is_action {
            self.msg_area
                .set_style(SegStyle::SchemeStyle(SchemeStyle::UserMsg));
            self.msg_area.add_text("** ");
        }

        {
            let nick_color = self.get_nick_color(sender);
            let style = SegStyle::Index(nick_color);
            self.msg_area.set_style(style);
            self.msg_area.add_text(sender);
        }

        self.msg_area
            .set_style(SegStyle::SchemeStyle(SchemeStyle::UserMsg));

        if !is_action {
            self.msg_area.add_char(':');
        }
        self.msg_area.add_char(' ');

        if highlight {
            self.msg_area
                .set_style(SegStyle::SchemeStyle(SchemeStyle::Highlight));
        }

        self.msg_area.add_text(msg);
        self.msg_area.flush_line();
    }

    pub(crate) fn add_msg(&mut self, msg: &str, ts: Timestamp) {
        self.reset_activity_line();

        self.add_timestamp(ts);
        self.msg_area
            .set_style(SegStyle::SchemeStyle(SchemeStyle::UserMsg));
        self.msg_area.add_text(msg);
        self.msg_area.flush_line();
    }

    pub(crate) fn add_err_msg(&mut self, msg: &str, ts: Timestamp) {
        self.reset_activity_line();

        self.add_timestamp(ts);
        self.msg_area
            .set_style(SegStyle::SchemeStyle(SchemeStyle::ErrMsg));
        self.msg_area.add_text(msg);
        self.msg_area.flush_line();
    }

    pub(crate) fn clear(&mut self) {
        self.msg_area.clear();
    }

    fn get_nick_color(&self, sender: &str) -> usize {
        // Anything works as long as it's fast
        let mut hash: usize = 5381;
        for c in sender.chars() {
            hash = hash.wrapping_mul(33).wrapping_add(c as usize);
        }
        hash
    }
}

////////////////////////////////////////////////////////////////////////////////
// Keeping nick list up-to-date

impl MessagingUI {
    pub(crate) fn clear_nicks(&mut self) {
        self.nicks.clear();
    }

    pub(crate) fn join(&mut self, nick: &str, ts: Option<Timestamp>) {
        if self.show_status && !self.nicks.contains(nick) {
            if let Some(ts) = ts {
                let line_idx = self.get_activity_line_idx(ts);
                self.msg_area.modify_line(line_idx, |line| {
                    line.set_style(SegStyle::SchemeStyle(SchemeStyle::Join));
                    line.add_char('+');
                    line.set_style(SegStyle::SchemeStyle(SchemeStyle::Faded));
                    line.add_text(nick);
                });
            }
        }

        self.nicks.insert(nick);
    }

    pub(crate) fn part(&mut self, nick: &str, ts: Option<Timestamp>) {
        self.nicks.remove(nick);

        if self.show_status {
            if let Some(ts) = ts {
                let line_idx = self.get_activity_line_idx(ts);
                self.msg_area.modify_line(line_idx, |line| {
                    line.set_style(SegStyle::SchemeStyle(SchemeStyle::Part));
                    line.add_char('-');
                    line.set_style(SegStyle::SchemeStyle(SchemeStyle::Faded));
                    line.add_text(nick);
                });
            }
        }
    }

    /// `state` == `None` means toggle
    /// `state` == `Some(state)` means set it to `state`
    pub(crate) fn set_or_toggle_ignore(&mut self, state: Option<bool>) {
        self.show_status = state.unwrap_or(!self.show_status);
        if self.show_status {
            self.add_client_notify_msg("Ignore disabled");
        } else {
            self.add_client_notify_msg("Ignore enabled");
        }
    }

    pub(crate) fn get_ignore_state(&self) -> bool {
        self.show_status
    }

    pub(crate) fn nick(&mut self, old_nick: &str, new_nick: &str, ts: Timestamp) {
        self.nicks.remove(old_nick);
        self.nicks.insert(new_nick);

        let line_idx = self.get_activity_line_idx(ts);
        self.msg_area.modify_line(line_idx, |line| {
            line.set_style(SegStyle::SchemeStyle(SchemeStyle::Faded));
            line.add_text(old_nick);
            line.set_style(SegStyle::SchemeStyle(SchemeStyle::Nick));
            line.add_char('>');
            line.set_style(SegStyle::SchemeStyle(SchemeStyle::Faded));
            line.add_text(new_nick);
        });
    }

    fn reset_activity_line(&mut self) {
        self.last_activity_line = None;
    }

    fn get_activity_line_idx(&mut self, ts: Timestamp) -> usize {
        match self.last_activity_line {
            Some(ref l) if l.ts == ts => {
                let line_idx = l.line_idx;
                // FIXME: It's a bit hacky to add a space in this function which from the name
                // looks like a getter.
                // The idea is that we want to add a space *before* adding new stuff, not *after*,
                // to avoid adding redundant spaces. The test `small_screen_1` breaks if we don't
                // get this right.
                self.msg_area
                    .modify_line(line_idx, |line| line.add_char(' '));
                line_idx
            }
            _ => {
                self.add_timestamp(ts);
                let line_idx = self.msg_area.flush_line();
                self.last_activity_line = Some(ActivityLine { ts, line_idx });
                line_idx
            }
        }
    }
}
<|endoftext|>


<commit_before>use crate::convert::*;
use std::hash::{Hasher};
use std::intrinsics::assume;

///Just a simple bit pattern.
const PAD : u128 = 0xF0E1_D2C3_B4A5_9687_7869_5A4B_3C2D_1E0F;

/// A `Hasher` for hashing an arbitrary stream of bytes.
///
/// Instances of [AHasher] represent state that is updated while hashing data.
///
/// Each method updates the internal state based on the new data provided. Once
/// all of the data has been provided, the resulting hash can be obtained by calling
/// `finish()`
///
/// [Clone] is also provided in case you wish to calculate hashes for two different items that
/// start with the same data.
///
#[derive(Debug, Clone)]
pub struct AHasher {
    buffer: [u64; 2],
}

impl AHasher {
    /// Creates a new hasher keyed to the provided keys.
    /// # Example
    ///
    /// ```
    /// use std::hash::Hasher;
    /// use ahash::AHasher;
    ///
    /// let mut hasher = AHasher::new_with_keys(123, 456);
    ///
    /// hasher.write_u32(1989);
    /// hasher.write_u8(11);
    /// hasher.write_u8(9);
    /// hasher.write(b"Huh?");
    ///
    /// println!("Hash is {:x}!", hasher.finish());
    /// ```
    #[inline]
    pub(crate) fn new_with_keys(key0: u64, key1: u64) -> AHasher {
        AHasher { buffer: [key0, key1] }
    }
}

#[inline(never)]
#[no_mangle]
fn hash_test_aes(input: &[u8]) -> u64 {
    let mut a = AHasher::new_with_keys(67, 87);
    a.write(input);
    a.finish()
}

/// Provides methods to hash all of the primitive types.
impl Hasher for AHasher {
    #[inline]
    fn write_u8(&mut self, i: u8) {
        self.write_u128(i as u128);
    }

    #[inline]
    fn write_u16(&mut self, i: u16) {
        self.write_u128(i as u128);
    }

    #[inline]
    fn write_u32(&mut self, i: u32) {
        self.write_u128(i as u128);
    }

    #[inline]
    fn write_u128(&mut self, i: u128) {
        self.buffer = aeshash(self.buffer.convert(),i).convert();
    }

    #[inline]
    fn write_usize(&mut self, i: usize) {
        self.write_u64(i as u64);
    }

    #[inline]
    fn write_u64(&mut self, i: u64) {
        self.write_u128(i as u128);
    }

    #[inline]
    fn write(&mut self, input: &[u8]) {
        let mut data = input;
        let length = data.len() as u64;
        //This will be scrambled by the first AES round in any branch.
        self.buffer[1] ^= length;
        //A 'binary search' on sizes reduces the number of comparisons.
        if data.len() >= 8 {
            if data.len() > 16 {
                if data.len() > 128 {
                    let mut par_block: u128 = self.buffer.convert();
                    while data.len() > 128 {
                        let (b1, rest) = data.read_u128();
                        par_block = aeshash(par_block, b1);
                        data = rest;
                        let (b2, rest) = data.read_u128();
                        self.buffer = aeshash(self.buffer.convert(), b2).convert();
                        data = rest;
                    }
                    self.buffer = aeshash(self.buffer.convert(), par_block).convert();
                }
                while data.len() > 32 {
                    //len 33-128
                    let (block, rest) = data.read_u128();
                    self.buffer = aeshash(self.buffer.convert(), block).convert();
                    data = rest;
                }
                unsafe{assume(data.len() > 16)} //This is always true because of the loop conditional above
                //len 17-32
                let (block, _) = data.read_u128();
                self.buffer = aeshash(self.buffer.convert(), block).convert();
                let block = data.read_last_u128();
                self.buffer = aeshash(self.buffer.convert(), block).convert();
            } else {
                //len 8-16
                let block: [u64; 2] = [data.read_u64().0, data.read_last_u64()];
                self.buffer = aeshash(self.buffer.convert(),block.convert()).convert();
            }
        } else {
            if data.len() >= 2 {
                if data.len() >= 4 {
                    //len 4-7
                    let block: [u32; 2] = [data.read_u32().0, data.read_last_u32()];
                    let block: [u64;2] = [block[1] as u64, block[0] as u64];
                    self.buffer = aeshash(self.buffer.convert(),block.convert()).convert()
                } else {
                    //len 2-3
                    let block: [u16; 2] = [data.read_u16().0, data.read_last_u16()];
                    let block: u32 = block.convert();
                    self.buffer = aeshash(self.buffer.convert(), block as u128).convert();
                }
            } else {
                if data.len() > 0 {
                    //len 1
                    self.buffer = aeshash(self.buffer.convert(), data[0] as u128).convert();
                }
            }
        }
    }
    #[inline]
    fn finish(&self) -> u64 {
        let result: [u64; 2] = aeshash(aeshash(self.buffer.convert(), PAD), PAD).convert();
        result[0]//.wrapping_add(result[1])
    }
}

#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes"))]
#[inline(always)]
fn aeshash(value: u128, xor: u128) -> u128 {
    use std::mem::transmute;
    #[cfg(target_arch = "x86")]
    use core::arch::x86::*;
    #[cfg(target_arch = "x86_64")]
    use std::arch::x86_64::*;
    unsafe {
        let value = transmute(value);
        transmute(_mm_aesdec_si128(value, transmute(xor)))
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;
    use std::hash::{BuildHasherDefault};
    use crate::convert::Convert;
    use crate::aes_hash::*;

    #[test]
    fn test_builder() {
        let mut map = HashMap::<u32, u64, BuildHasherDefault<AHasher>>::default();
        map.insert(1, 3);
    }

    #[test]
    fn test_default() {
        let hasher_a = AHasher::default();
        assert_ne!(0, hasher_a.buffer[0]);
        assert_ne!(0, hasher_a.buffer[1]);
        assert_ne!(hasher_a.buffer[0], hasher_a.buffer[1]);
        let hasher_b = AHasher::default();
        assert_eq!(hasher_a.buffer[0], hasher_b.buffer[0]);
        assert_eq!(hasher_a.buffer[1], hasher_b.buffer[1]);
    }

    #[test]
    fn test_hash() {
        let mut result: [u64; 2] = [0x6c62272e07bb0142, 0x62b821756295c58d];
        let value: [u64; 2] = [1 << 32, 0xFEDCBA9876543210];
        result = aeshash(value.convert(), result.convert()).convert();
        result = aeshash(result.convert(), result.convert()).convert();
        let mut result2: [u64; 2] = [0x6c62272e07bb0142, 0x62b821756295c58d];
        let value2: [u64; 2] = [1, 0xFEDCBA9876543210];
        result2 = aeshash(value2.convert(), result2.convert()).convert();
        result2 = aeshash(result2.convert(), result.convert()).convert();
        let result: [u8; 16] = result.convert();
        let result2: [u8; 16] = result2.convert();
        assert_ne!(hex::encode(result), hex::encode(result2));
    }

    #[test]
    fn test_conversion() {
        let input: &[u8] = "dddddddd".as_bytes();
        let bytes: u64 = as_array!(input, 8).convert();
        assert_eq!(bytes, 0x6464646464646464);
    }
}
<commit_msg>Perfrom two hashes instead of loading data twice.<commit_after>use crate::convert::*;
use std::hash::{Hasher};
use std::intrinsics::assume;

///Just a simple bit pattern.
const PAD : u128 = 0xF0E1_D2C3_B4A5_9687_7869_5A4B_3C2D_1E0F;

/// A `Hasher` for hashing an arbitrary stream of bytes.
///
/// Instances of [AHasher] represent state that is updated while hashing data.
///
/// Each method updates the internal state based on the new data provided. Once
/// all of the data has been provided, the resulting hash can be obtained by calling
/// `finish()`
///
/// [Clone] is also provided in case you wish to calculate hashes for two different items that
/// start with the same data.
///
#[derive(Debug, Clone)]
pub struct AHasher {
    buffer: [u64; 2],
}

impl AHasher {
    /// Creates a new hasher keyed to the provided keys.
    /// # Example
    ///
    /// ```
    /// use std::hash::Hasher;
    /// use ahash::AHasher;
    ///
    /// let mut hasher = AHasher::new_with_keys(123, 456);
    ///
    /// hasher.write_u32(1989);
    /// hasher.write_u8(11);
    /// hasher.write_u8(9);
    /// hasher.write(b"Huh?");
    ///
    /// println!("Hash is {:x}!", hasher.finish());
    /// ```
    #[inline]
    pub(crate) fn new_with_keys(key0: u64, key1: u64) -> AHasher {
        AHasher { buffer: [key0, key1] }
    }
}

#[inline(never)]
#[no_mangle]
fn hash_test_aes(input: &[u8]) -> u64 {
    let mut a = AHasher::new_with_keys(67, 87);
    a.write(input);
    a.finish()
}

/// Provides methods to hash all of the primitive types.
impl Hasher for AHasher {
    #[inline]
    fn write_u8(&mut self, i: u8) {
        self.write_u128(i as u128);
    }

    #[inline]
    fn write_u16(&mut self, i: u16) {
        self.write_u128(i as u128);
    }

    #[inline]
    fn write_u32(&mut self, i: u32) {
        self.write_u128(i as u128);
    }

    #[inline]
    fn write_u128(&mut self, i: u128) {
        self.buffer = aeshash(self.buffer.convert(),i).convert();
    }

    #[inline]
    fn write_usize(&mut self, i: usize) {
        self.write_u64(i as u64);
    }

    #[inline]
    fn write_u64(&mut self, i: u64) {
        self.write_u128(i as u128);
    }

    #[inline]
    fn write(&mut self, input: &[u8]) {
        let mut data = input;
        let length = data.len() as u64;
        //This will be scrambled by the first AES round in any branch.
        self.buffer[1] ^= length;
        //A 'binary search' on sizes reduces the number of comparisons.
        if data.len() > 8 {
            if data.len() > 16 {
                if data.len() > 128 {
                    let mut par_block: u128 = self.buffer.convert();
                    while data.len() > 128 {
                        let (b1, rest) = data.read_u128();
                        par_block = aeshash(par_block, b1);
                        data = rest;
                        let (b2, rest) = data.read_u128();
                        self.buffer = aeshash(self.buffer.convert(), b2).convert();
                        data = rest;
                    }
                    self.buffer = aeshash(self.buffer.convert(), par_block).convert();
                }
                while data.len() > 32 {
                    //len 33-128
                    let (block, rest) = data.read_u128();
                    self.buffer = aeshash(self.buffer.convert(), block).convert();
                    data = rest;
                }
                unsafe{assume(data.len() > 16)} //This is always true because of the loop conditional above
                //len 17-32
                let (block, _) = data.read_u128();
                self.buffer = aeshash(self.buffer.convert(), block).convert();
                let block = data.read_last_u128();
                self.buffer = aeshash(self.buffer.convert(), block).convert();
            } else {
                //len 9-16
                self.buffer = aeshash(self.buffer.convert(),data.read_u64().0 as u128).convert();
                self.buffer = aeshash(self.buffer.convert(),data.read_last_u64() as u128).convert();
            }
        } else {
            if data.len() >= 2 {
                if data.len() >= 4 {
                    //len 4-8
                    self.buffer = aeshash(self.buffer.convert(),data.read_u32().0 as u128).convert();
                    self.buffer = aeshash(self.buffer.convert(),data.read_last_u32() as u128).convert();
                } else {
                    //len 2-3
                    self.buffer = aeshash(self.buffer.convert(),data.read_u16().0 as u128).convert();
                    self.buffer = aeshash(self.buffer.convert(),data.read_last_u16() as u128).convert();
                }
            } else {
                if data.len() > 0 {
                    //len 1
                    self.buffer = aeshash(self.buffer.convert(), data[0] as u128).convert();
                }
            }
        }
    }
    #[inline]
    fn finish(&self) -> u64 {
        let result: [u64; 2] = aeshash(aeshash(self.buffer.convert(), PAD), PAD).convert();
        result[0]//.wrapping_add(result[1])
    }
}

#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes"))]
#[inline(always)]
fn aeshash(value: u128, xor: u128) -> u128 {
    use std::mem::transmute;
    #[cfg(target_arch = "x86")]
    use core::arch::x86::*;
    #[cfg(target_arch = "x86_64")]
    use std::arch::x86_64::*;
    unsafe {
        let value = transmute(value);
        transmute(_mm_aesdec_si128(value, transmute(xor)))
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;
    use std::hash::{BuildHasherDefault};
    use crate::convert::Convert;
    use crate::aes_hash::*;

    #[test]
    fn test_builder() {
        let mut map = HashMap::<u32, u64, BuildHasherDefault<AHasher>>::default();
        map.insert(1, 3);
    }

    #[test]
    fn test_default() {
        let hasher_a = AHasher::default();
        assert_ne!(0, hasher_a.buffer[0]);
        assert_ne!(0, hasher_a.buffer[1]);
        assert_ne!(hasher_a.buffer[0], hasher_a.buffer[1]);
        let hasher_b = AHasher::default();
        assert_eq!(hasher_a.buffer[0], hasher_b.buffer[0]);
        assert_eq!(hasher_a.buffer[1], hasher_b.buffer[1]);
    }

    #[test]
    fn test_hash() {
        let mut result: [u64; 2] = [0x6c62272e07bb0142, 0x62b821756295c58d];
        let value: [u64; 2] = [1 << 32, 0xFEDCBA9876543210];
        result = aeshash(value.convert(), result.convert()).convert();
        result = aeshash(result.convert(), result.convert()).convert();
        let mut result2: [u64; 2] = [0x6c62272e07bb0142, 0x62b821756295c58d];
        let value2: [u64; 2] = [1, 0xFEDCBA9876543210];
        result2 = aeshash(value2.convert(), result2.convert()).convert();
        result2 = aeshash(result2.convert(), result.convert()).convert();
        let result: [u8; 16] = result.convert();
        let result2: [u8; 16] = result2.convert();
        assert_ne!(hex::encode(result), hex::encode(result2));
    }

    #[test]
    fn test_conversion() {
        let input: &[u8] = "dddddddd".as_bytes();
        let bytes: u64 = as_array!(input, 8).convert();
        assert_eq!(bytes, 0x6464646464646464);
    }
}
<|endoftext|>


<commit_before>use clap::{Arg, App};
use chrono::*;
use filter;

#[derive(Debug)]
#[derive(PartialEq)]
pub struct RequestLogAnalyzerArgs {
    pub filename: String,
    pub conditions: filter::FilterConditions,
    pub graphite_server: Option<String>,
    pub graphite_port: Option<u16>,
    pub graphite_prefix: Option<String>,
}

pub fn parse_args<'a, T>(args: T) -> Result<RequestLogAnalyzerArgs, &'a str>
    where T: IntoIterator<Item = String>
{
    let app = App::new("Request.log Analyzer")
        .author(crate_authors!())
        .version(crate_version!())
        .after_help(crate_description!())
        .arg(Arg::with_name("filename")
            .index(1)
            .value_name("FILE")
            .required(false)
            .help("Log file to analyze, defaults to stdin")
            .takes_value(true))
        .arg(Arg::with_name("time_filter_minutes")
            .value_name("MINUTES")
            .short("t")
            .help("Limit to the last n minutes")
            .takes_value(true))
        .arg(Arg::with_name("include_term")
            .value_name("TERM")
            .long("include")
            .help("Only include lines that contain one of these terms")
            .takes_value(true)
            .multiple(true)
            .number_of_values(1))
        .arg(Arg::with_name("exclude_term")
            .value_name("TERM")
            .long("exclude")
            .help("Exclude lines that contain one of these terms")
            .takes_value(true)
            .multiple(true)
            .number_of_values(1))
        .arg(Arg::with_name("graphite-server")
            .value_name("GRAPHITE_SERVER")
            .long("graphite-server")
            .help("Send values to this Graphite server instead of stdout")
            .takes_value(true))
        .arg(Arg::with_name("graphite-port")
            .value_name("GRAPHITE_PORT")
            .long("graphite-port")
            .takes_value(true)
            .default_value("2003"))
        .arg(Arg::with_name("graphite-prefix")
            .value_name("GRAPHITE_PREFIX")
            .long("graphite-prefix")
            .help("Prefix for Graphite key, e.g. 'servers.prod.publisher1'")
            .takes_value(true))
        .get_matches_from(args);

    let filename = app.value_of("filename").unwrap_or("-").to_string();

    let conditions = filter::FilterConditions {
        include_terms: match app.values_of("include_term") {
            Some(values) => Some(values.map(|v| v.to_string()).collect()),
            None => None,
        },
        exclude_terms: match app.values_of("exclude_term") {
            Some(values) => Some(values.map(|v| v.to_string()).collect()),
            None => None,
        },
        latest_time: match app.value_of("time_filter_minutes") {
            Some(minutes) => {
                Some(Duration::minutes(minutes.parse().expect("Minutes value must be numeric")))
            }
            None => None,
        },
    };

    let graphite_server = match app.value_of("graphite-server") {
        Some(value) => Some(String::from(value)),
        None => None,
    };

    let graphite_port: Option<u16> = match app.value_of("graphite-port") {
        Some(value) => Some(value.parse().expect("Port number must be numeric.")),
        None => None,
    };

    let graphite_prefix = match app.value_of("graphite-prefix") {
        Some(value) => Some(String::from(value)),
        None => None,
    };

    Ok(RequestLogAnalyzerArgs {
        filename: filename,
        conditions: conditions,
        graphite_server: graphite_server,
        graphite_port: graphite_port,
        graphite_prefix: graphite_prefix,
    })
}

#[cfg(test)]
mod tests {
    use filter;
    use chrono::*;
    use super::*;

    #[test]
    fn test_parse_args_default() {
        let raw_args = vec!["request_log_analyzer".to_string()];

        let expected = RequestLogAnalyzerArgs {
            filename: String::from("-"),
            conditions: filter::FilterConditions {
                include_terms: None,
                exclude_terms: None,
                latest_time: None,
            },
            graphite_server: None,
            graphite_port: Some(2003),
            graphite_prefix: None,
        };

        let result = parse_args(raw_args).unwrap();

        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_args_all() {
        let raw_args = vec![String::from("request_log_analyzer"),
                            String::from("--include"),
                            String::from("one"),
                            String::from("--exclude"),
                            String::from("this other"),
                            String::from("-t"),
                            String::from("10"),
                            String::from("my-logfile.log"),
                            String::from("--graphite-server"),
                            String::from("localhost"),
                            String::from("--graphite-port"),
                            String::from("4000"),
                            String::from("--graphite-prefix"),
                            String::from("prod")];

        let expected = RequestLogAnalyzerArgs {
            filename: String::from("my-logfile.log"),
            conditions: filter::FilterConditions {
                include_terms: Some(vec![String::from("one")]),
                exclude_terms: Some(vec![String::from("this other")]),
                latest_time: Some(Duration::minutes(10)),
            },
            graphite_server: Some(String::from("localhost")),
            graphite_port: Some(4000),
            graphite_prefix: Some(String::from("prod")),
        };

        let result = parse_args(raw_args).unwrap();

        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_args_mutliple_include_exclude() {
        let raw_args = vec![String::from("request_log_analyzer"),
                            String::from("--include"),
                            String::from("one"),
                            String::from("--include"),
                            String::from("two"),
                            String::from("--exclude"),
                            String::from("this other"),
                            String::from("--exclude"),
                            String::from("more"),
                            String::from("my-logfile.log")];

        let expected = RequestLogAnalyzerArgs {
            filename: String::from("my-logfile.log"),
            conditions: filter::FilterConditions {
                include_terms: Some(vec![String::from("one"), String::from("two")]),
                exclude_terms: Some(vec![String::from("this other"), String::from("more")]),
                latest_time: None,
            },
            graphite_server: None,
            graphite_port: Some(2003),
            graphite_prefix: None,
        };

        let result = parse_args(raw_args).unwrap();

        assert_eq!(result, expected);
    }
}
<commit_msg>Fix typo<commit_after>use clap::{Arg, App};
use chrono::*;
use filter;

#[derive(Debug)]
#[derive(PartialEq)]
pub struct RequestLogAnalyzerArgs {
    pub filename: String,
    pub conditions: filter::FilterConditions,
    pub graphite_server: Option<String>,
    pub graphite_port: Option<u16>,
    pub graphite_prefix: Option<String>,
}

pub fn parse_args<'a, T>(args: T) -> Result<RequestLogAnalyzerArgs, &'a str>
    where T: IntoIterator<Item = String>
{
    let app = App::new("Request.log Analyzer")
        .author(crate_authors!())
        .version(crate_version!())
        .after_help(crate_description!())
        .arg(Arg::with_name("filename")
            .index(1)
            .value_name("FILE")
            .required(false)
            .help("Log file to analyze, defaults to stdin")
            .takes_value(true))
        .arg(Arg::with_name("time_filter_minutes")
            .value_name("MINUTES")
            .short("t")
            .help("Limit to the last n minutes")
            .takes_value(true))
        .arg(Arg::with_name("include_term")
            .value_name("TERM")
            .long("include")
            .help("Only include lines that contain one of these terms")
            .takes_value(true)
            .multiple(true)
            .number_of_values(1))
        .arg(Arg::with_name("exclude_term")
            .value_name("TERM")
            .long("exclude")
            .help("Exclude lines that contain one of these terms")
            .takes_value(true)
            .multiple(true)
            .number_of_values(1))
        .arg(Arg::with_name("graphite-server")
            .value_name("GRAPHITE_SERVER")
            .long("graphite-server")
            .help("Send values to this Graphite server instead of stdout")
            .takes_value(true))
        .arg(Arg::with_name("graphite-port")
            .value_name("GRAPHITE_PORT")
            .long("graphite-port")
            .takes_value(true)
            .default_value("2003"))
        .arg(Arg::with_name("graphite-prefix")
            .value_name("GRAPHITE_PREFIX")
            .long("graphite-prefix")
            .help("Prefix for Graphite key, e.g. 'servers.prod.publisher1'")
            .takes_value(true))
        .get_matches_from(args);

    let filename = app.value_of("filename").unwrap_or("-").to_string();

    let conditions = filter::FilterConditions {
        include_terms: match app.values_of("include_term") {
            Some(values) => Some(values.map(|v| v.to_string()).collect()),
            None => None,
        },
        exclude_terms: match app.values_of("exclude_term") {
            Some(values) => Some(values.map(|v| v.to_string()).collect()),
            None => None,
        },
        latest_time: match app.value_of("time_filter_minutes") {
            Some(minutes) => {
                Some(Duration::minutes(minutes.parse().expect("Minutes value must be numeric")))
            }
            None => None,
        },
    };

    let graphite_server = match app.value_of("graphite-server") {
        Some(value) => Some(String::from(value)),
        None => None,
    };

    let graphite_port: Option<u16> = match app.value_of("graphite-port") {
        Some(value) => Some(value.parse().expect("Port number must be numeric.")),
        None => None,
    };

    let graphite_prefix = match app.value_of("graphite-prefix") {
        Some(value) => Some(String::from(value)),
        None => None,
    };

    Ok(RequestLogAnalyzerArgs {
        filename: filename,
        conditions: conditions,
        graphite_server: graphite_server,
        graphite_port: graphite_port,
        graphite_prefix: graphite_prefix,
    })
}

#[cfg(test)]
mod tests {
    use filter;
    use chrono::*;
    use super::*;

    #[test]
    fn test_parse_args_default() {
        let raw_args = vec!["request_log_analyzer".to_string()];

        let expected = RequestLogAnalyzerArgs {
            filename: String::from("-"),
            conditions: filter::FilterConditions {
                include_terms: None,
                exclude_terms: None,
                latest_time: None,
            },
            graphite_server: None,
            graphite_port: Some(2003),
            graphite_prefix: None,
        };

        let result = parse_args(raw_args).unwrap();

        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_args_all() {
        let raw_args = vec![String::from("request_log_analyzer"),
                            String::from("--include"),
                            String::from("one"),
                            String::from("--exclude"),
                            String::from("this other"),
                            String::from("-t"),
                            String::from("10"),
                            String::from("my-logfile.log"),
                            String::from("--graphite-server"),
                            String::from("localhost"),
                            String::from("--graphite-port"),
                            String::from("4000"),
                            String::from("--graphite-prefix"),
                            String::from("prod")];

        let expected = RequestLogAnalyzerArgs {
            filename: String::from("my-logfile.log"),
            conditions: filter::FilterConditions {
                include_terms: Some(vec![String::from("one")]),
                exclude_terms: Some(vec![String::from("this other")]),
                latest_time: Some(Duration::minutes(10)),
            },
            graphite_server: Some(String::from("localhost")),
            graphite_port: Some(4000),
            graphite_prefix: Some(String::from("prod")),
        };

        let result = parse_args(raw_args).unwrap();

        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_args_multiple_include_exclude() {
        let raw_args = vec![String::from("request_log_analyzer"),
                            String::from("--include"),
                            String::from("one"),
                            String::from("--include"),
                            String::from("two"),
                            String::from("--exclude"),
                            String::from("this other"),
                            String::from("--exclude"),
                            String::from("more"),
                            String::from("my-logfile.log")];

        let expected = RequestLogAnalyzerArgs {
            filename: String::from("my-logfile.log"),
            conditions: filter::FilterConditions {
                include_terms: Some(vec![String::from("one"), String::from("two")]),
                exclude_terms: Some(vec![String::from("this other"), String::from("more")]),
                latest_time: None,
            },
            graphite_server: None,
            graphite_port: Some(2003),
            graphite_prefix: None,
        };

        let result = parse_args(raw_args).unwrap();

        assert_eq!(result, expected);
    }
}
<|endoftext|>


<commit_before>use cocoa::base::id;
use cocoa::foundation::NSString;
use metal_sys::{MTLArgument, MTLArgumentAccess, MTLArgumentType, MTLDataType};
use objc::runtime::YES;
use std::borrow::Cow;
use std::ffi::CStr;
use std::mem;

pub struct Argument(id);

impl Argument {
    pub fn name(&self) -> Cow<str> {
        unsafe { CStr::from_ptr(self.0.name().UTF8String()).to_string_lossy() }
    }

    pub fn access(&self) -> ArgumentAccess {
        unsafe { self.0.access().into() }
    }

    pub fn is_active(&self) -> bool {
        unsafe { self.0.active() == YES }
    }

    pub fn index(&self) -> usize {
        unsafe { self.0.index() as usize }
    }

    pub fn get_type(&self) -> ArgumentType {
        unsafe { self.0.getType().into() }
    }

    pub fn buffer_alignment(&self) -> Option<usize> {
        if self.get_type() == ArgumentType::Buffer {
            unsafe { Some(self.0.bufferAlignment() as usize) }
        } else {
            None
        }
    }

    pub fn buffer_data_size(&self) -> Option<usize> {
        if self.get_type() == ArgumentType::Buffer {
            unsafe { Some(self.0.bufferDataSize() as usize) }
        } else {
            None
        }
    }

    pub fn buffer_struct_type(&self) -> ! {
        unimplemented!()
    }

    pub fn texture_data_type(&self) -> ! {
        unimplemented!()
    }

    pub fn threadgroup_memory_alignment(&self) -> Option<usize> {
        if self.get_type() == ArgumentType::ThreadgroupMemory {
            unsafe { Some(self.0.threadgroupMemoryAlignment() as usize) }
        } else {
            None
        }
    }

    pub fn threadgroup_memory_data_size(&self) -> Option<usize> {
        if self.get_type() == ArgumentType::ThreadgroupMemory {
            unsafe { Some(self.0.threadgroupMemoryDataSize() as usize) }
        } else {
            None
        }
    }
}

#[repr(usize)]
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub enum ArgumentAccess {
    ReadOnly,
    ReadWrite,
    WriteOnly
}

impl From<MTLArgumentAccess> for ArgumentAccess {
    fn from(argument_access: MTLArgumentAccess) -> Self {
        unsafe { mem::transmute(argument_access) }
    }
}

impl Into<MTLArgumentAccess> for ArgumentAccess {
    fn into(self) -> MTLArgumentAccess {
        unsafe { mem::transmute(self) }
    }
}

#[repr(usize)]
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub enum ArgumentType {
    Buffer,
    ThreadgroupMemory,
    Texture,
    Sampler
}

impl From<MTLArgumentType> for ArgumentType {
    fn from(argument_type: MTLArgumentType) -> Self {
        unsafe { mem::transmute(argument_type) }
    }
}

impl Into<MTLArgumentType> for ArgumentType {
    fn into(self) -> MTLArgumentType {
        unsafe { mem::transmute(self) }
    }
}

#[repr(usize)]
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub enum DataType {
    None,
    Struct,
    Array,

    Float,
    Float2,
    Float3,
    Float4,

    Float2x2,
    Float2x3,
    Float2x4,

    Float3x2,
    Float3x3,
    Float3x4,
    Float4x2,
    Float4x3,
    Float4x4,

    Half,
    Half2,
    Half3,
    Half4,

    Half2x2,
    Half2x3,
    Half2x4,

    Half3x2,
    Half3x3,
    Half3x4,

    Half4x2,
    Half4x3,
    Half4x4,

    Int,
    Int2,
    Int3,
    Int4,
    UInt,
    UInt2,
    UInt3,
    UInt4,

    Short,
    Short2,
    Short3,
    Short4,

    UShort,
    UShort2,
    UShort3,
    UShort4,

    Char,
    Char2,
    Char3,
    Char4,

    UChar,
    UChar2,
    UChar3,
    UChar4,

    Bool,
    Bool2,
    Bool3,
    Bool4
}

impl From<MTLDataType> for DataType {
    fn from(data_type: MTLDataType) -> Self {
        unsafe { mem::transmute(data_type) }
    }
}

impl Into<MTLDataType> for DataType {
    fn into(self) -> MTLDataType {
        unsafe { mem::transmute(self) }
    }
}
<commit_msg>Add  Argument::texture_type(&self) stub<commit_after>use cocoa::base::id;
use cocoa::foundation::NSString;
use metal_sys::{MTLArgument, MTLArgumentAccess, MTLArgumentType, MTLDataType};
use objc::runtime::YES;
use std::borrow::Cow;
use std::ffi::CStr;
use std::mem;

pub struct Argument(id);

impl Argument {
    pub fn name(&self) -> Cow<str> {
        unsafe { CStr::from_ptr(self.0.name().UTF8String()).to_string_lossy() }
    }

    pub fn access(&self) -> ArgumentAccess {
        unsafe { self.0.access().into() }
    }

    pub fn is_active(&self) -> bool {
        unsafe { self.0.active() == YES }
    }

    pub fn index(&self) -> usize {
        unsafe { self.0.index() as usize }
    }

    pub fn get_type(&self) -> ArgumentType {
        unsafe { self.0.getType().into() }
    }

    pub fn buffer_alignment(&self) -> Option<usize> {
        if self.get_type() == ArgumentType::Buffer {
            unsafe { Some(self.0.bufferAlignment() as usize) }
        } else {
            None
        }
    }

    pub fn buffer_data_size(&self) -> Option<usize> {
        if self.get_type() == ArgumentType::Buffer {
            unsafe { Some(self.0.bufferDataSize() as usize) }
        } else {
            None
        }
    }

    pub fn buffer_struct_type(&self) -> ! {
        unimplemented!()
    }

    pub fn texture_data_type(&self) -> ! {
        unimplemented!()
    }

    pub fn texture_type(&self) -> ! {
        unimplemented!()
    }

    pub fn threadgroup_memory_alignment(&self) -> Option<usize> {
        if self.get_type() == ArgumentType::ThreadgroupMemory {
            unsafe { Some(self.0.threadgroupMemoryAlignment() as usize) }
        } else {
            None
        }
    }

    pub fn threadgroup_memory_data_size(&self) -> Option<usize> {
        if self.get_type() == ArgumentType::ThreadgroupMemory {
            unsafe { Some(self.0.threadgroupMemoryDataSize() as usize) }
        } else {
            None
        }
    }
}

#[repr(usize)]
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub enum ArgumentAccess {
    ReadOnly,
    ReadWrite,
    WriteOnly
}

impl From<MTLArgumentAccess> for ArgumentAccess {
    fn from(argument_access: MTLArgumentAccess) -> Self {
        unsafe { mem::transmute(argument_access) }
    }
}

impl Into<MTLArgumentAccess> for ArgumentAccess {
    fn into(self) -> MTLArgumentAccess {
        unsafe { mem::transmute(self) }
    }
}

#[repr(usize)]
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub enum ArgumentType {
    Buffer,
    ThreadgroupMemory,
    Texture,
    Sampler
}

impl From<MTLArgumentType> for ArgumentType {
    fn from(argument_type: MTLArgumentType) -> Self {
        unsafe { mem::transmute(argument_type) }
    }
}

impl Into<MTLArgumentType> for ArgumentType {
    fn into(self) -> MTLArgumentType {
        unsafe { mem::transmute(self) }
    }
}

#[repr(usize)]
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub enum DataType {
    None,
    Struct,
    Array,

    Float,
    Float2,
    Float3,
    Float4,

    Float2x2,
    Float2x3,
    Float2x4,

    Float3x2,
    Float3x3,
    Float3x4,
    Float4x2,
    Float4x3,
    Float4x4,

    Half,
    Half2,
    Half3,
    Half4,

    Half2x2,
    Half2x3,
    Half2x4,

    Half3x2,
    Half3x3,
    Half3x4,

    Half4x2,
    Half4x3,
    Half4x4,

    Int,
    Int2,
    Int3,
    Int4,
    UInt,
    UInt2,
    UInt3,
    UInt4,

    Short,
    Short2,
    Short3,
    Short4,

    UShort,
    UShort2,
    UShort3,
    UShort4,

    Char,
    Char2,
    Char3,
    Char4,

    UChar,
    UChar2,
    UChar3,
    UChar4,

    Bool,
    Bool2,
    Bool3,
    Bool4
}

impl From<MTLDataType> for DataType {
    fn from(data_type: MTLDataType) -> Self {
        unsafe { mem::transmute(data_type) }
    }
}

impl Into<MTLDataType> for DataType {
    fn into(self) -> MTLDataType {
        unsafe { mem::transmute(self) }
    }
}
<|endoftext|>


<commit_before>use {
    ImageSize,
};

/// Implemented by all graphics back-ends.
/// This trait uses default methods to simplify implementation.
///
/// ## Alpha blending
/// The default behavior for alpha blending is assumed to be turned off.
/// A back-end might choose to turn it on by default and ignore enable/disable.
/// The default behavior for not supporting alpha blending is ignoring enable/disable.
/// Alpha blending is assumed to be expensive and turned off when not needed.
/// For cases when alpha blending is explicitly not wanted there will be own methods,
/// in case the back-end needs to restore to its own default afterwards.
pub trait BackEnd<I: ImageSize> {
    /// Returns true if feature is supported.
    #[inline(always)]
    fn supports_clear_rgba(&self) -> bool { false }

    /// Clears background with a color.
    fn clear_rgba(&mut self, _r: f32, _g: f32, _b: f32, _a: f32) {}

    /// Turns on alpha blending.
    fn enable_alpha_blend(&mut self) {}

    /// Turns off alpha blending.
    fn disable_alpha_blend(&mut self) {}

    /// Returns true if texture feature is supported.
    #[inline(always)]
    fn supports_single_texture(&self) -> bool { false }

    /// Sets the current single-texture.
    fn enable_single_texture(&mut self, _texture: &I) {}

    /// Disables single-texture.
    fn disable_single_texture(&mut self) {}

    /// Should return true if texture has alpha channel.
    ///
    /// This will enable alpha blending.
    /// Alpha blending might be enabled if color per vertex has alpha.
    /// Ignore if alpha blending is not supported.
    /// Ignore if alpha channel is not used.
    #[inline(always)]
    fn has_texture_alpha(&self, _texture: &I) -> bool { false }

    /// Returns true if feature is supported.
    #[inline(always)]
    fn supports_tri_list_xy_f64_rgba_f32(&self) -> bool { false }

    /// Renders list of 2d triangles with color assigned per vertex.
    fn tri_list_xy_f64_rgba_f32(
        &mut self,
        _vertices: &[f64],
        _colors: &[f32]
    ) {}

    /// Returns true if feature is supported.
    #[inline(always)]
    fn supports_tri_list_xy_f32_rgba_f32(&self) -> bool { false }

    /// Renders list of 2d triangles with color assigned per vertex.
    fn tri_list_xy_f32_rgba_f32(
        &mut self,
        _vertices: &[f32],
        _colors: &[f32]
    ) {}

    /// Returns true if feature is supported.
    #[inline(always)]
    fn supports_tri_list_xy_f32_rgba_f32_uv_f32(&self) -> bool { false }

    /// Renders list of 2d triangles.
    ///
    /// A color and a texture coordinate is assigned per vertex.
    /// The texture coordinates refers to the current single-texture.
    fn tri_list_xy_f32_rgba_f32_uv_f32(
        &mut self,
        _vertices: &[f32],
        _colors: &[f32],
        _texture_coords: &[f32]
    ) {}
}

<commit_msg>Removed xy_64 methods in BackEnd<commit_after>use {
    ImageSize,
};

/// Implemented by all graphics back-ends.
/// This trait uses default methods to simplify implementation.
///
/// ## Alpha blending
/// The default behavior for alpha blending is assumed to be turned off.
/// A back-end might choose to turn it on by default and ignore enable/disable.
/// The default behavior for not supporting alpha blending is ignoring enable/disable.
/// Alpha blending is assumed to be expensive and turned off when not needed.
/// For cases when alpha blending is explicitly not wanted there will be own methods,
/// in case the back-end needs to restore to its own default afterwards.
pub trait BackEnd<I: ImageSize> {
    /// Returns true if feature is supported.
    #[inline(always)]
    fn supports_clear_rgba(&self) -> bool { false }

    /// Clears background with a color.
    fn clear_rgba(&mut self, _r: f32, _g: f32, _b: f32, _a: f32) {}

    /// Turns on alpha blending.
    fn enable_alpha_blend(&mut self) {}

    /// Turns off alpha blending.
    fn disable_alpha_blend(&mut self) {}

    /// Returns true if texture feature is supported.
    #[inline(always)]
    fn supports_single_texture(&self) -> bool { false }

    /// Sets the current single-texture.
    fn enable_single_texture(&mut self, _texture: &I) {}

    /// Disables single-texture.
    fn disable_single_texture(&mut self) {}

    /// Should return true if texture has alpha channel.
    ///
    /// This will enable alpha blending.
    /// Alpha blending might be enabled if color per vertex has alpha.
    /// Ignore if alpha blending is not supported.
    /// Ignore if alpha channel is not used.
    #[inline(always)]
    fn has_texture_alpha(&self, _texture: &I) -> bool { false }

    /// Returns true if feature is supported.
    #[inline(always)]
    fn supports_tri_list_xy_f32_rgba_f32(&self) -> bool { false }

    /// Renders list of 2d triangles with color assigned per vertex.
    fn tri_list_xy_f32_rgba_f32(
        &mut self,
        _vertices: &[f32],
        _colors: &[f32]
    ) {}

    /// Returns true if feature is supported.
    #[inline(always)]
    fn supports_tri_list_xy_f32_rgba_f32_uv_f32(&self) -> bool { false }

    /// Renders list of 2d triangles.
    ///
    /// A color and a texture coordinate is assigned per vertex.
    /// The texture coordinates refers to the current single-texture.
    fn tri_list_xy_f32_rgba_f32_uv_f32(
        &mut self,
        _vertices: &[f32],
        _colors: &[f32],
        _texture_coords: &[f32]
    ) {}
}

<|endoftext|>