<commit_before>use std::io::{self, Write};

use futures::{future, Async, Future, Poll, Sink, Stream};
use futures::sync::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};

use termion::event::{Event, Key};
use tokio::run;
use xrl::{AvailablePlugins, Client, ConfigChanged, Frontend, FrontendBuilder,
          PluginStarted, PluginStoped, ScrollTo, ServerResult, Style, ThemeChanged, Update,
          UpdateCmds};

use xdg::BaseDirectories;
use failure::Error;

use core::{Terminal, TerminalEvent, Command};
use widgets::{Editor, CommandPrompt};

pub struct Tui {
    pub editor: Editor,
    pub prompt: Option<CommandPrompt>,
    pub term: Terminal,
    pub term_size: (u16, u16),
    pub shutdown: bool,
}

impl Tui {
    pub fn new(
        mut client: Client,
        events: UnboundedReceiver<CoreEvent>,
    ) -> Result<Self, Error> {

        let conf_dir = BaseDirectories::with_prefix("xi")
            .ok()
            .and_then(|dirs| Some(dirs.get_config_home().to_string_lossy().into_owned()));
        run(
            client
                .client_started(conf_dir.as_ref().map(|dir| &**dir), None)
                .map_err(|_| ()),
        );

        Ok(Tui {
            term: Terminal::new()?,
            shutdown: false,
            term_size: (0, 0),
            editor: Editor::new(client, events),
            prompt: None,
        })
    }

    fn handle_resize(&mut self, size: (u16, u16)) {
        self.term_size = size;
        self.editor.handle_resize(size);
    }

    fn exit(&mut self) {
        self.shutdown = true;
    }

    pub fn handle_cmd(&mut self, cmd: Command) {
        match cmd {
            Command::Cancel => {
                self.prompt = None;
            },
            Command::Quit => self.exit(),
            Command::Save(view) => self.editor.save(view),
            Command::Invalid(cmd) => {
                error!("Received invalid editor command: {}", cmd);
            }
        }
    }

    /// Global keybindings can be parsed here
    fn handle_input(&mut self, event: Event) {
        match event {
            Event::Key(Key::Ctrl('c')) => self.exit(),
            Event::Key(Key::Alt('x')) => {
                if let Some(ref mut prompt) = self.prompt {
                    prompt.handle_input(event);
                } else {
                    self.prompt = Some(CommandPrompt::default());
                }
            },
            event => {
                let mut out = None;
                if let Some(ref mut prompt) = self.prompt {
                    if let Some(cmd) = prompt.handle_input(event) {
                        out = Some(cmd);
                    }
                } else {
                    self.editor.handle_input(event);
                }
                if let Some(out) = out {
                    self.handle_cmd(out);
                    self.prompt = None;
                }
            }
        }
    }

    /// Check and handle terminal events.
    fn process_terminal_events(&mut self) {
        let mut new_size: Option<(u16, u16)> = None;
        loop {
            match self.term.poll() {
                 Ok(Async::Ready(Some(event))) => match event {
                    TerminalEvent::Resize(size) => {
                        new_size = Some(size);
                    }
                    TerminalEvent::Input(input) => self.handle_input(input),
                },
                Ok(Async::Ready(None)) => {
                    error!("terminal stream shut down => exiting");
                    self.shutdown = true;
                }
                Ok(Async::NotReady) => break,
                Err(_) => {
                    error!("error while polling terminal stream => exiting");
                    self.shutdown = true;
                }
            }
        }
        if let Some(size) = new_size {
            if !self.shutdown {
                self.handle_resize(size);
            }
        }
    }

    fn render(&mut self) -> Result<(), Error> {
        if let Some(ref mut prompt) = self.prompt {
            prompt.render(self.term.stdout(), self.term_size.1)?;
        } else {
            self.editor.render(self.term.stdout())?;
        }
        if let Err(e) = self.term.stdout().flush() {
            error!("failed to flush stdout: {}", e);
        }
        Ok(())
    }
}

#[derive(Debug)]
pub enum CoreEvent {
    Update(Update),
    ScrollTo(ScrollTo),
    SetStyle(Style),
}

impl Future for Tui {
    type Item = ();
    type Error = io::Error;

    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        self.editor.process_open_requests();
        self.editor.process_delayed_events();
        self.process_terminal_events();
        self.editor.process_core_events();

        if let Err(e) = self.render() {
            error!("error: {}", e);
            error!("caused by: {}", e.cause());
        }

        if self.shutdown {
            Ok(Async::Ready(()))
        } else {
            Ok(Async::NotReady)
        }
    }
}

/// Actual frontend that implementes `xrl::Frontend`
/// It's future is the main loop of xi-term.
pub struct TuiService(UnboundedSender<CoreEvent>);

impl TuiService {
    fn send_core_event(&mut self, event: CoreEvent) -> ServerResult<()> {
        if let Err(e) = self.0.start_send(event) {
            let e = format!("failed to send core event to TUI: {}", e);
            error!("{}", e);
            return Box::new(future::err(e.into()));
        }
        match self.0.poll_complete() {
            Ok(_) => Box::new(future::ok(())),
            Err(e) => {
                let e = format!("failed to send core event to TUI: {}", e);
                Box::new(future::err(e.into()))
            }
        }
    }
}

impl Frontend for TuiService {
    fn update(&mut self, update: Update) -> ServerResult<()> {
        self.send_core_event(CoreEvent::Update(update))
    }

    fn scroll_to(&mut self, scroll_to: ScrollTo) -> ServerResult<()> {
        self.send_core_event(CoreEvent::ScrollTo(scroll_to))
    }

    fn def_style(&mut self, style: Style) -> ServerResult<()> {
        self.send_core_event(CoreEvent::SetStyle(style))
    }
    fn available_plugins(&mut self, _plugins: AvailablePlugins) -> ServerResult<()> {
        Box::new(future::ok(()))
    }
    fn update_cmds(&mut self, _plugins: UpdateCmds) -> ServerResult<()> {
        Box::new(future::ok(()))
    }
    fn plugin_started(&mut self, _plugins: PluginStarted) -> ServerResult<()> {
        Box::new(future::ok(()))
    }
    fn plugin_stoped(&mut self, _plugin: PluginStoped) -> ServerResult<()> {
        Box::new(future::ok(()))
    }
    fn config_changed(&mut self, _config: ConfigChanged) -> ServerResult<()> {
        Box::new(future::ok(()))
    }
    fn theme_changed(&mut self, _theme: ThemeChanged) -> ServerResult<()> {
        Box::new(future::ok(()))
    }
}

pub struct TuiServiceBuilder(UnboundedSender<CoreEvent>);

impl TuiServiceBuilder {
    pub fn new() -> (Self, UnboundedReceiver<CoreEvent>) {
        let (tx, rx) = unbounded();
        (TuiServiceBuilder(tx), rx)
    }
}

impl FrontendBuilder<TuiService> for TuiServiceBuilder {
    fn build(self, _client: Client) -> TuiService {
        TuiService(self.0)
    }
}
<commit_msg>cleanup command panel logic<commit_after>use std::io::{self, Write};

use futures::{future, Async, Future, Poll, Sink, Stream};
use futures::sync::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};

use termion::event::{Event, Key};
use tokio::run;
use xrl::{AvailablePlugins, Client, ConfigChanged, Frontend, FrontendBuilder,
          PluginStarted, PluginStoped, ScrollTo, ServerResult, Style, ThemeChanged, Update,
          UpdateCmds};

use xdg::BaseDirectories;
use failure::Error;

use core::{Terminal, TerminalEvent, Command};
use widgets::{Editor, CommandPrompt};

pub struct Tui {
    pub editor: Editor,
    pub prompt: Option<CommandPrompt>,
    pub term: Terminal,
    pub term_size: (u16, u16),
    pub shutdown: bool,
}

impl Tui {
    pub fn new(
        mut client: Client,
        events: UnboundedReceiver<CoreEvent>,
    ) -> Result<Self, Error> {

        let conf_dir = BaseDirectories::with_prefix("xi")
            .ok()
            .and_then(|dirs| Some(dirs.get_config_home().to_string_lossy().into_owned()));
        run(
            client
                .client_started(conf_dir.as_ref().map(|dir| &**dir), None)
                .map_err(|_| ()),
        );

        Ok(Tui {
            term: Terminal::new()?,
            shutdown: false,
            term_size: (0, 0),
            editor: Editor::new(client, events),
            prompt: None,
        })
    }

    fn handle_resize(&mut self, size: (u16, u16)) {
        self.term_size = size;
        self.editor.handle_resize(size);
    }

    fn exit(&mut self) {
        self.shutdown = true;
    }

    pub fn handle_cmd(&mut self, cmd: Command) {
        match cmd {
            Command::Cancel => {
                self.prompt = None;
            },
            Command::Quit => self.exit(),
            Command::Save(view) => self.editor.save(view),
            Command::Invalid(cmd) => {
                error!("Received invalid editor command: {}", cmd);
            }
        }
    }

    /// Global keybindings can be parsed here
    fn handle_input(&mut self, event: Event) {
        match event {
            Event::Key(Key::Ctrl('c')) => self.exit(),
            Event::Key(Key::Alt('x')) => {
                if let Some(ref mut prompt) = self.prompt {
                    prompt.handle_input(event);
                } else {
                    self.prompt = Some(CommandPrompt::default());
                }
            },
            event => {
                // No command prompt is active, process the event normally.
                if self.prompt.is_none() {
                    self.editor.handle_input(event);
                    return;
                }

                // A command prompt is active.
                let mut prompt = self.prompt.take().unwrap();
                if let Some(cmd) = prompt.handle_input(event) {
                    // The event resulted in a command to process
                    self.handle_cmd(cmd);
                } else {
                    // Still not command, just update the prompt
                    self.prompt = Some(prompt);
                }
            }
        }
    }

    /// Check and handle terminal events.
    fn process_terminal_events(&mut self) {
        let mut new_size: Option<(u16, u16)> = None;
        loop {
            match self.term.poll() {
                 Ok(Async::Ready(Some(event))) => match event {
                    TerminalEvent::Resize(size) => {
                        new_size = Some(size);
                    }
                    TerminalEvent::Input(input) => self.handle_input(input),
                },
                Ok(Async::Ready(None)) => {
                    error!("terminal stream shut down => exiting");
                    self.shutdown = true;
                }
                Ok(Async::NotReady) => break,
                Err(_) => {
                    error!("error while polling terminal stream => exiting");
                    self.shutdown = true;
                }
            }
        }
        if let Some(size) = new_size {
            if !self.shutdown {
                self.handle_resize(size);
            }
        }
    }

    fn render(&mut self) -> Result<(), Error> {
        if let Some(ref mut prompt) = self.prompt {
            prompt.render(self.term.stdout(), self.term_size.1)?;
        } else {
            self.editor.render(self.term.stdout())?;
        }
        if let Err(e) = self.term.stdout().flush() {
            error!("failed to flush stdout: {}", e);
        }
        Ok(())
    }
}

#[derive(Debug)]
pub enum CoreEvent {
    Update(Update),
    ScrollTo(ScrollTo),
    SetStyle(Style),
}

impl Future for Tui {
    type Item = ();
    type Error = io::Error;

    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        self.editor.process_open_requests();
        self.editor.process_delayed_events();
        self.process_terminal_events();
        self.editor.process_core_events();

        if let Err(e) = self.render() {
            error!("error: {}", e);
            error!("caused by: {}", e.cause());
        }

        if self.shutdown {
            Ok(Async::Ready(()))
        } else {
            Ok(Async::NotReady)
        }
    }
}

/// Actual frontend that implementes `xrl::Frontend`
/// It's future is the main loop of xi-term.
pub struct TuiService(UnboundedSender<CoreEvent>);

impl TuiService {
    fn send_core_event(&mut self, event: CoreEvent) -> ServerResult<()> {
        if let Err(e) = self.0.start_send(event) {
            let e = format!("failed to send core event to TUI: {}", e);
            error!("{}", e);
            return Box::new(future::err(e.into()));
        }
        match self.0.poll_complete() {
            Ok(_) => Box::new(future::ok(())),
            Err(e) => {
                let e = format!("failed to send core event to TUI: {}", e);
                Box::new(future::err(e.into()))
            }
        }
    }
}

impl Frontend for TuiService {
    fn update(&mut self, update: Update) -> ServerResult<()> {
        self.send_core_event(CoreEvent::Update(update))
    }

    fn scroll_to(&mut self, scroll_to: ScrollTo) -> ServerResult<()> {
        self.send_core_event(CoreEvent::ScrollTo(scroll_to))
    }

    fn def_style(&mut self, style: Style) -> ServerResult<()> {
        self.send_core_event(CoreEvent::SetStyle(style))
    }
    fn available_plugins(&mut self, _plugins: AvailablePlugins) -> ServerResult<()> {
        Box::new(future::ok(()))
    }
    fn update_cmds(&mut self, _plugins: UpdateCmds) -> ServerResult<()> {
        Box::new(future::ok(()))
    }
    fn plugin_started(&mut self, _plugins: PluginStarted) -> ServerResult<()> {
        Box::new(future::ok(()))
    }
    fn plugin_stoped(&mut self, _plugin: PluginStoped) -> ServerResult<()> {
        Box::new(future::ok(()))
    }
    fn config_changed(&mut self, _config: ConfigChanged) -> ServerResult<()> {
        Box::new(future::ok(()))
    }
    fn theme_changed(&mut self, _theme: ThemeChanged) -> ServerResult<()> {
        Box::new(future::ok(()))
    }
}

pub struct TuiServiceBuilder(UnboundedSender<CoreEvent>);

impl TuiServiceBuilder {
    pub fn new() -> (Self, UnboundedReceiver<CoreEvent>) {
        let (tx, rx) = unbounded();
        (TuiServiceBuilder(tx), rx)
    }
}

impl FrontendBuilder<TuiService> for TuiServiceBuilder {
    fn build(self, _client: Client) -> TuiService {
        TuiService(self.0)
    }
}
<|endoftext|>


<commit_before>// Copyright (C) 2019 Inderjit Gill

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

mod error;
mod render_gl;

use clap::{value_t, App, Arg};
use config;
use env_logger;
use image::GenericImageView;
use log::info;

use gl;
use imgui;
use imgui_opengl_renderer;
use imgui_sdl2;
use sdl2;

use crate::error::Result;
use std::path::Path;

use core::BitmapInfo;

use std::ffi;

#[macro_export]
macro_rules! c_str {
    ($literal:expr) => {
        ffi::CStr::from_bytes_with_nul_unchecked(concat!($literal, "\0").as_bytes())
    };
}

fn main() -> Result<()> {
    // Add in `./Config.toml`
    // Add in config from the environment (with a prefix of SENI)
    // Eg.. `SENI_DEBUG=1 ./target/app` would set the `debug` key
    //
    let mut config = config::Config::default();
    config
        .merge(config::File::with_name("Config"))?
        .merge(config::Environment::with_prefix("SENI"))?;

    // update config with command line options
    //
    let matches = App::new("seni-gui")
        .version("4.1.0")
        .author("Inderjit Gill <email@indy.io>")
        .about("native gui build of seni")
        .arg(
            Arg::with_name("SCRIPT")
                .help("Sets the input seni script to use")
                .index(1),
        )
        .arg(
            Arg::with_name("seed")
                .short("s")
                .long("seed")
                .help("The seed to use")
                .takes_value(true),
        )
        .get_matches();

    env_logger::init();

    if let Some(script) = matches.value_of("SCRIPT") {
        // this should always pass as SCRIPT is required
        info!("Using script file: {}", script);

        config.set("script", script)?;
    }

    if let Ok(seed) = value_t!(matches.value_of("seed"), i64) {
        config.set("seed", seed)?;
    }

    run(&config)
}

fn load_texture(ppath: &Path, name: &str) -> Result<BitmapInfo> {
    let path = ppath.join(name);

    info!("load_bitmap: {:?}", path);
    let image = image::open(&path)?;

    let (w, h) = image.dimensions();
    let width = w as usize;
    let height = h as usize;
    let mut data: Vec<u8> = Vec::with_capacity(width * height * 4);

    info!("loading bitmap {} of size {} x {}", name, width, height);

    for (_, _, rgba) in image.pixels() {
        data.push(rgba.data[0]);
        data.push(rgba.data[1]);
        data.push(rgba.data[2]);
        data.push(rgba.data[3]);
    }

    let bitmap_info = BitmapInfo {
        width,
        height,
        data,
        ..Default::default()
    };

    Ok(bitmap_info)
}

fn identity() -> [f32; 16] {
    let out: [f32; 16] = [
        1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
    ];
    out
}

fn ortho(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> [f32; 16] {
    let lr = 1.0 / (left - right);
    let bt = 1.0 / (bottom - top);
    let nf = 1.0 / (near - far);

    let out: [f32; 16] = [
        -2.0 * lr,
        0.0,
        0.0,
        0.0,
        0.0,
        -2.0 * bt,
        0.0,
        0.0,
        0.0,
        0.0,
        2.0 * nf,
        0.0,
        (left + right) * lr,
        (top + bottom) * bt,
        (far + near) * nf,
        1.0,
    ];

    out
}

fn run(config: &config::Config) -> Result<()> {
    let assets_location = config.get_str("assets")?;
    let assets_path = Path::new(&assets_location);

    let bitmap_info = load_texture(&assets_path, "textures/texture.png")?;

    let projection_matrix = ortho(0.0, 1920.0, 0.0, 1062.0, 10.0, -10.0);
    // let projection_matrix = ortho(0.0, 1000.0, 0.0, 1000.0, 10.0, -10.0);
    let model_view_matrix = identity();

    let sdl_context = sdl2::init()?;
    let video = sdl_context.video()?;

    {
        let gl_attr = video.gl_attr();
        gl_attr.set_context_profile(sdl2::video::GLProfile::Core);
        gl_attr.set_context_version(4, 5);
    }

    let window = video
        .window("seni-gui", 1000, 1000)
        .position_centered()
        .resizable()
        .opengl()
        .allow_highdpi()
        .build()?;

    let _gl_context = window
        .gl_create_context()
        .expect("Couldn't create GL context");
    // provide a function to load function pointer by string
    let gl = gl::Gl::load_with(|s| video.gl_get_proc_address(s) as _);

    let mut imgui = imgui::ImGui::init();
    imgui.set_ini_filename(None);

    let mut imgui_sdl2 = imgui_sdl2::ImguiSdl2::new(&mut imgui);

    let renderer =
        imgui_opengl_renderer::Renderer::new(&mut imgui, |s| video.gl_get_proc_address(s) as _);

    // --------------------------------------------------------------------------------
    // set up shader program
    //
    let shader_program = render_gl::Program::from_path(&gl, &assets_path, "shaders/triangle")?;

    // set up vertex buffer object
    //
    let vertices: Vec<f32> = vec![
        // pos      // colour           // uv
        // x,  y,   r,   g,   b,   a,   u,   v
        1.0 * 700.0,
        0.5 * 700.0,
        1.0,
        0.0,
        0.0,
        1.0,
        0.0,
        0.0, // bottom right
        0.0 * 700.0,
        0.5 * 700.0,
        0.0,
        1.0,
        0.0,
        1.0,
        1.0,
        0.0, // bottom left
        0.5 * 700.0,
        1.5 * 700.0,
        0.0,
        0.0,
        1.0,
        1.0,
        1.0,
        1.0, // top
        1.5 * 700.0,
        0.25 * 700.0,
        1.0,
        0.0,
        0.0,
        0.1,
        0.0,
        0.0, // bottom right
        0.5 * 700.0,
        0.25 * 700.0,
        0.0,
        1.0,
        0.0,
        0.1,
        1.0,
        0.0, // bottom left
        1.0 * 700.0,
        1.25 * 700.0,
        0.0,
        0.0,
        1.0,
        0.1,
        1.0,
        1.0, // top
    ];

    let mut vbo: gl::types::GLuint = 0;
    let mut vao: gl::types::GLuint = 0;
    let mut tex: gl::types::GLuint = 0;

    let gl_int_p_matrix: gl::types::GLint;
    let gl_int_mv_matrix: gl::types::GLint;

    shader_program.set_used();
    unsafe {
        // set up vertex buffer object
        //
        gl.GenBuffers(1, &mut vbo);
        gl.BindBuffer(gl::ARRAY_BUFFER, vbo);
        gl.BufferData(
            gl::ARRAY_BUFFER,                                                       // target
            (vertices.len() * std::mem::size_of::<f32>()) as gl::types::GLsizeiptr, // size of data in bytes
            vertices.as_ptr() as *const gl::types::GLvoid, // pointer to data
            gl::STATIC_DRAW,                               // usage
        );
        gl.BindBuffer(gl::ARRAY_BUFFER, 0);

        // set up vertex array object
        //
        gl.GenVertexArrays(1, &mut vao);
        gl.BindVertexArray(vao);
        gl.BindBuffer(gl::ARRAY_BUFFER, vbo);

        gl.EnableVertexAttribArray(0); // this is "layout (location = 0)" in vertex shader
        gl.VertexAttribPointer(
            0,         // index of the generic vertex attribute ("layout (location = 0)")
            2,         // the number of components per generic vertex attribute
            gl::FLOAT, // data type
            gl::FALSE, // normalized (int-to-float conversion)
            (8 * std::mem::size_of::<f32>()) as gl::types::GLint, // stride (byte offset between consecutive attributes)
            std::ptr::null(),                                     // offset of the first component
        );
        gl.EnableVertexAttribArray(1); // this is "layout (location = 1)" in vertex shader
        gl.VertexAttribPointer(
            1,         // index of the generic vertex attribute ("layout (location = 1)")
            4,         // the number of components per generic vertex attribute
            gl::FLOAT, // data type
            gl::FALSE, // normalized (int-to-float conversion)
            (8 * std::mem::size_of::<f32>()) as gl::types::GLint, // stride (byte offset between consecutive attributes)
            (2 * std::mem::size_of::<f32>()) as *const gl::types::GLvoid, // offset of the first component
        );
        gl.EnableVertexAttribArray(2); // this is "layout (location = 2)" in vertex shader
        gl.VertexAttribPointer(
            2,         // index of the generic vertex attribute ("layout (location = 2)")
            2,         // the number of components per generic vertex attribute
            gl::FLOAT, // data type
            gl::FALSE, // normalized (int-to-float conversion)
            (8 * std::mem::size_of::<f32>()) as gl::types::GLint, // stride (byte offset between consecutive attributes)
            (6 * std::mem::size_of::<f32>()) as *const gl::types::GLvoid, // offset of the first component
        );

        gl.BindBuffer(gl::ARRAY_BUFFER, 0);
        gl.BindVertexArray(0);

        // set up shared state for window
        //
        gl.Viewport(0, 0, 900, 700);
        gl.ClearColor(1.0, 1.0, 1.0, 1.0);

        // assuming that we'll be using pre-multiplied alpha
        // see http://www.realtimerendering.com/blog/gpus-prefer-premultiplication/
        gl.Enable(gl::BLEND);
        gl.BlendEquation(gl::FUNC_ADD);
        gl.BlendFunc(gl::ONE, gl::ONE_MINUS_SRC_ALPHA);

        gl.GenTextures(1, &mut tex);
        // "Bind" the newly created texture : all future texture functions will modify this texture
        gl.BindTexture(gl::TEXTURE_2D, tex);

        // Give the image to OpenGL
        gl.TexImage2D(
            gl::TEXTURE_2D,
            0,
            gl::RGBA as i32,
            bitmap_info.width as i32,
            bitmap_info.height as i32,
            0,
            gl::RGBA,
            gl::UNSIGNED_BYTE,
            bitmap_info.data.as_ptr() as *const gl::types::GLvoid,
        );

        gl.TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
        gl.TexParameteri(
            gl::TEXTURE_2D,
            gl::TEXTURE_MIN_FILTER,
            gl::LINEAR_MIPMAP_LINEAR as i32,
        );
        gl.GenerateMipmap(gl::TEXTURE_2D);

        gl.ActiveTexture(gl::TEXTURE0);

        let loc = gl.GetUniformLocation(shader_program.id(), c_str!("myTextureSampler").as_ptr());
        gl.Uniform1i(loc, 0);

        gl_int_p_matrix = gl.GetUniformLocation(shader_program.id(), c_str!("uPMatrix").as_ptr());
        gl.UniformMatrix4fv(gl_int_p_matrix, 1, gl::FALSE, projection_matrix.as_ptr());

        gl_int_mv_matrix = gl.GetUniformLocation(shader_program.id(), c_str!("uMVMatrix").as_ptr());
        gl.UniformMatrix4fv(gl_int_mv_matrix, 1, gl::FALSE, model_view_matrix.as_ptr());
    }

    // --------------------------------------------------------------------------------

    let mut event_pump = sdl_context.event_pump()?;

    'running: loop {
        use sdl2::event::Event;
        use sdl2::keyboard::Keycode;

        for event in event_pump.poll_iter() {
            imgui_sdl2.handle_event(&mut imgui, &event);
            if imgui_sdl2.ignore_event(&event) {
                continue;
            }

            match event {
                Event::Quit { .. }
                | Event::KeyDown {
                    keycode: Some(Keycode::Escape),
                    ..
                } => break 'running,
                // http://nercury.github.io/rust/opengl/tutorial/2018/07/27/opengl-in-rust-from-scratch-13-safe-triangle-nalgebra.html
                // Event::Window {
                //     win_event: WindowEvent::Resized(w, h),
                //     ..
                // } => {
                //     viewport.update_size(w, h);
                //     viewport.set_used(&gl);
                // },
                _ => {}
            }
        }

        let ui = imgui_sdl2.frame(&window, &mut imgui, &event_pump.mouse_state());
        ui.show_demo_window(&mut true);

        let (window_width, window_height) = window.size();
        unsafe {
            gl.Viewport(0, 0, window_width as i32, window_height as i32);
            gl.Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
        }

        // draw triangle

        // shader_program.set_used();
        unsafe {
            gl.BindVertexArray(vao);
            gl.DrawArrays(
                gl::TRIANGLES, // mode
                0,             // starting index in the enabled arrays
                6,             // number of indices to be rendered
            );
        }

        renderer.render(ui);

        window.gl_swap_window();

        ::std::thread::sleep(::std::time::Duration::new(0, 1_000_000_000u32 / 60));
    }

    Ok(())
}
<commit_msg>resizing viewport only during sdl2 resize events<commit_after>// Copyright (C) 2019 Inderjit Gill

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

mod error;
mod render_gl;

use clap::{value_t, App, Arg};
use config;
use env_logger;
use image::GenericImageView;
use log::info;

use gl;
use imgui;
use imgui_opengl_renderer;
use imgui_sdl2;
use sdl2;

use crate::error::Result;
use std::path::Path;

use core::BitmapInfo;

use std::ffi;

#[macro_export]
macro_rules! c_str {
    ($literal:expr) => {
        ffi::CStr::from_bytes_with_nul_unchecked(concat!($literal, "\0").as_bytes())
    };
}

fn main() -> Result<()> {
    // Add in `./Config.toml`
    // Add in config from the environment (with a prefix of SENI)
    // Eg.. `SENI_DEBUG=1 ./target/app` would set the `debug` key
    //
    let mut config = config::Config::default();
    config
        .merge(config::File::with_name("Config"))?
        .merge(config::Environment::with_prefix("SENI"))?;

    // update config with command line options
    //
    let matches = App::new("seni-gui")
        .version("4.1.0")
        .author("Inderjit Gill <email@indy.io>")
        .about("native gui build of seni")
        .arg(
            Arg::with_name("SCRIPT")
                .help("Sets the input seni script to use")
                .index(1),
        )
        .arg(
            Arg::with_name("seed")
                .short("s")
                .long("seed")
                .help("The seed to use")
                .takes_value(true),
        )
        .get_matches();

    env_logger::init();

    if let Some(script) = matches.value_of("SCRIPT") {
        // this should always pass as SCRIPT is required
        info!("Using script file: {}", script);

        config.set("script", script)?;
    }

    if let Ok(seed) = value_t!(matches.value_of("seed"), i64) {
        config.set("seed", seed)?;
    }

    run(&config)
}

fn load_texture(ppath: &Path, name: &str) -> Result<BitmapInfo> {
    let path = ppath.join(name);

    info!("load_bitmap: {:?}", path);
    let image = image::open(&path)?;

    let (w, h) = image.dimensions();
    let width = w as usize;
    let height = h as usize;
    let mut data: Vec<u8> = Vec::with_capacity(width * height * 4);

    info!("loading bitmap {} of size {} x {}", name, width, height);

    for (_, _, rgba) in image.pixels() {
        data.push(rgba.data[0]);
        data.push(rgba.data[1]);
        data.push(rgba.data[2]);
        data.push(rgba.data[3]);
    }

    let bitmap_info = BitmapInfo {
        width,
        height,
        data,
        ..Default::default()
    };

    Ok(bitmap_info)
}

fn identity() -> [f32; 16] {
    let out: [f32; 16] = [
        1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
    ];
    out
}

fn ortho(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> [f32; 16] {
    let lr = 1.0 / (left - right);
    let bt = 1.0 / (bottom - top);
    let nf = 1.0 / (near - far);

    let out: [f32; 16] = [
        -2.0 * lr,
        0.0,
        0.0,
        0.0,
        0.0,
        -2.0 * bt,
        0.0,
        0.0,
        0.0,
        0.0,
        2.0 * nf,
        0.0,
        (left + right) * lr,
        (top + bottom) * bt,
        (far + near) * nf,
        1.0,
    ];

    out
}

fn run(config: &config::Config) -> Result<()> {
    let assets_location = config.get_str("assets")?;
    let assets_path = Path::new(&assets_location);

    let bitmap_info = load_texture(&assets_path, "textures/texture.png")?;

    let projection_matrix = ortho(0.0, 1920.0, 0.0, 1062.0, 10.0, -10.0);
    // let projection_matrix = ortho(0.0, 1000.0, 0.0, 1000.0, 10.0, -10.0);
    let model_view_matrix = identity();

    let sdl_context = sdl2::init()?;
    let video = sdl_context.video()?;

    {
        let gl_attr = video.gl_attr();
        gl_attr.set_context_profile(sdl2::video::GLProfile::Core);
        gl_attr.set_context_version(4, 5);
    }

    let window = video
        .window("seni-gui", 1000, 1000)
        .position_centered()
        .resizable()
        .opengl()
        .allow_highdpi()
        .build()?;

    let _gl_context = window
        .gl_create_context()
        .expect("Couldn't create GL context");
    // provide a function to load function pointer by string
    let gl = gl::Gl::load_with(|s| video.gl_get_proc_address(s) as _);

    let mut imgui = imgui::ImGui::init();
    imgui.set_ini_filename(None);

    let mut imgui_sdl2 = imgui_sdl2::ImguiSdl2::new(&mut imgui);

    let renderer =
        imgui_opengl_renderer::Renderer::new(&mut imgui, |s| video.gl_get_proc_address(s) as _);

    // --------------------------------------------------------------------------------
    // set up shader program
    //
    let shader_program = render_gl::Program::from_path(&gl, &assets_path, "shaders/triangle")?;

    // set up vertex buffer object
    //
    let vertices: Vec<f32> = vec![
        // pos      // colour           // uv
        // x,  y,   r,   g,   b,   a,   u,   v
        1.0 * 700.0,
        0.5 * 700.0,
        1.0,
        0.0,
        0.0,
        1.0,
        0.0,
        0.0, // bottom right
        0.0 * 700.0,
        0.5 * 700.0,
        0.0,
        1.0,
        0.0,
        1.0,
        1.0,
        0.0, // bottom left
        0.5 * 700.0,
        1.5 * 700.0,
        0.0,
        0.0,
        1.0,
        1.0,
        1.0,
        1.0, // top
        1.5 * 700.0,
        0.25 * 700.0,
        1.0,
        0.0,
        0.0,
        0.1,
        0.0,
        0.0, // bottom right
        0.5 * 700.0,
        0.25 * 700.0,
        0.0,
        1.0,
        0.0,
        0.1,
        1.0,
        0.0, // bottom left
        1.0 * 700.0,
        1.25 * 700.0,
        0.0,
        0.0,
        1.0,
        0.1,
        1.0,
        1.0, // top
    ];

    let mut vbo: gl::types::GLuint = 0;
    let mut vao: gl::types::GLuint = 0;
    let mut tex: gl::types::GLuint = 0;

    let gl_int_p_matrix: gl::types::GLint;
    let gl_int_mv_matrix: gl::types::GLint;

    shader_program.set_used();
    unsafe {
        // set up vertex buffer object
        //
        gl.GenBuffers(1, &mut vbo);
        gl.BindBuffer(gl::ARRAY_BUFFER, vbo);
        gl.BufferData(
            gl::ARRAY_BUFFER,                                                       // target
            (vertices.len() * std::mem::size_of::<f32>()) as gl::types::GLsizeiptr, // size of data in bytes
            vertices.as_ptr() as *const gl::types::GLvoid, // pointer to data
            gl::STATIC_DRAW,                               // usage
        );
        gl.BindBuffer(gl::ARRAY_BUFFER, 0);

        // set up vertex array object
        //
        gl.GenVertexArrays(1, &mut vao);
        gl.BindVertexArray(vao);
        gl.BindBuffer(gl::ARRAY_BUFFER, vbo);

        gl.EnableVertexAttribArray(0); // this is "layout (location = 0)" in vertex shader
        gl.VertexAttribPointer(
            0,         // index of the generic vertex attribute ("layout (location = 0)")
            2,         // the number of components per generic vertex attribute
            gl::FLOAT, // data type
            gl::FALSE, // normalized (int-to-float conversion)
            (8 * std::mem::size_of::<f32>()) as gl::types::GLint, // stride (byte offset between consecutive attributes)
            std::ptr::null(),                                     // offset of the first component
        );
        gl.EnableVertexAttribArray(1); // this is "layout (location = 1)" in vertex shader
        gl.VertexAttribPointer(
            1,         // index of the generic vertex attribute ("layout (location = 1)")
            4,         // the number of components per generic vertex attribute
            gl::FLOAT, // data type
            gl::FALSE, // normalized (int-to-float conversion)
            (8 * std::mem::size_of::<f32>()) as gl::types::GLint, // stride (byte offset between consecutive attributes)
            (2 * std::mem::size_of::<f32>()) as *const gl::types::GLvoid, // offset of the first component
        );
        gl.EnableVertexAttribArray(2); // this is "layout (location = 2)" in vertex shader
        gl.VertexAttribPointer(
            2,         // index of the generic vertex attribute ("layout (location = 2)")
            2,         // the number of components per generic vertex attribute
            gl::FLOAT, // data type
            gl::FALSE, // normalized (int-to-float conversion)
            (8 * std::mem::size_of::<f32>()) as gl::types::GLint, // stride (byte offset between consecutive attributes)
            (6 * std::mem::size_of::<f32>()) as *const gl::types::GLvoid, // offset of the first component
        );

        gl.BindBuffer(gl::ARRAY_BUFFER, 0);
        gl.BindVertexArray(0);

        // set up shared state for window
        //
        gl.Viewport(0, 0, 900, 700);
        gl.ClearColor(1.0, 1.0, 1.0, 1.0);

        // assuming that we'll be using pre-multiplied alpha
        // see http://www.realtimerendering.com/blog/gpus-prefer-premultiplication/
        gl.Enable(gl::BLEND);
        gl.BlendEquation(gl::FUNC_ADD);
        gl.BlendFunc(gl::ONE, gl::ONE_MINUS_SRC_ALPHA);

        gl.GenTextures(1, &mut tex);
        // "Bind" the newly created texture : all future texture functions will modify this texture
        gl.BindTexture(gl::TEXTURE_2D, tex);

        // Give the image to OpenGL
        gl.TexImage2D(
            gl::TEXTURE_2D,
            0,
            gl::RGBA as i32,
            bitmap_info.width as i32,
            bitmap_info.height as i32,
            0,
            gl::RGBA,
            gl::UNSIGNED_BYTE,
            bitmap_info.data.as_ptr() as *const gl::types::GLvoid,
        );

        gl.TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
        gl.TexParameteri(
            gl::TEXTURE_2D,
            gl::TEXTURE_MIN_FILTER,
            gl::LINEAR_MIPMAP_LINEAR as i32,
        );
        gl.GenerateMipmap(gl::TEXTURE_2D);

        gl.ActiveTexture(gl::TEXTURE0);

        let loc = gl.GetUniformLocation(shader_program.id(), c_str!("myTextureSampler").as_ptr());
        gl.Uniform1i(loc, 0);

        gl_int_p_matrix = gl.GetUniformLocation(shader_program.id(), c_str!("uPMatrix").as_ptr());
        gl.UniformMatrix4fv(gl_int_p_matrix, 1, gl::FALSE, projection_matrix.as_ptr());

        gl_int_mv_matrix = gl.GetUniformLocation(shader_program.id(), c_str!("uMVMatrix").as_ptr());
        gl.UniformMatrix4fv(gl_int_mv_matrix, 1, gl::FALSE, model_view_matrix.as_ptr());
    }

    // --------------------------------------------------------------------------------

    let mut event_pump = sdl_context.event_pump()?;

    'running: loop {
        use sdl2::event::{Event, WindowEvent};
        use sdl2::keyboard::Keycode;

        for event in event_pump.poll_iter() {
            imgui_sdl2.handle_event(&mut imgui, &event);
            if imgui_sdl2.ignore_event(&event) {
                continue;
            }

            match event {
                Event::Quit { .. }
                | Event::KeyDown {
                    keycode: Some(Keycode::Escape),
                    ..
                } => break 'running,
                Event::Window {
                    win_event: WindowEvent::Resized(w, h),
                    ..
                } => unsafe {
                    gl.Viewport(0, 0, w as i32, h as i32);
                },
                _ => {}
            }
        }

        let ui = imgui_sdl2.frame(&window, &mut imgui, &event_pump.mouse_state());
        ui.show_demo_window(&mut true);

        unsafe {
            gl.Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
        }

        // draw triangle

        // shader_program.set_used();
        unsafe {
            gl.BindVertexArray(vao);
            gl.DrawArrays(
                gl::TRIANGLES, // mode
                0,             // starting index in the enabled arrays
                6,             // number of indices to be rendered
            );
        }

        renderer.render(ui);

        window.gl_swap_window();

        ::std::thread::sleep(::std::time::Duration::new(0, 1_000_000_000u32 / 60));
    }

    Ok(())
}
<|endoftext|>


<commit_before>
// make sure to add test calls to tests() func
#![forbid(dead_code)]

extern crate tempdir;

use cpp_parser;
use cpp_data::*;
use utils::PathBufPushTweak;
use std::fs;
use std::fs::File;
use std::io::Write;
use cpp_method::*;
use cpp_type::*;
use cpp_operator::CppOperator;

fn run_parser(code: &'static str) -> CppData {
  let dir = tempdir::TempDir::new("test_cpp_parser_run").unwrap();
  let include_dir = dir.path().with_added("include");
  fs::create_dir(&include_dir).unwrap();
  let include_name = "myfakelib.h";
  let include_file_path = include_dir.with_added(&include_name);
  {
    let mut include_file = File::create(&include_file_path).unwrap();
    include_file.write(code.as_bytes()).unwrap();
    include_file.write("\n".as_bytes()).unwrap();
  }
  let mut result = cpp_parser::run(cpp_parser::CppParserConfig {
    include_dirs: vec![include_dir],
    header_name: include_name.to_string(),
    tmp_cpp_path: dir.path().with_added("1.cpp"),
    name_blacklist: Vec::new(),
  });
  for method in &mut result.methods {
    if let Some(ref mut origin_location) = method.origin_location {
      assert_eq!(origin_location.include_file_path,
                 include_file_path.display().to_string());
      assert!(origin_location.line > 0);
    } else {
      panic!("no origin_location in pare result");
    }
    method.origin_location = None;
  }
  result
}

fn simple_func() {
  let data = run_parser("int func1(int x);");
  assert!(data.template_instantiations.is_empty());
  assert!(data.types.is_empty());
  assert!(data.methods.len() == 1);
  assert_eq!(data.methods[0],
             CppMethod {
               name: "func1".to_string(),
               class_membership: None,
               operator: None,
               return_type: Some(CppType {
                 indirection: CppTypeIndirection::None,
                 is_const: false,
                 base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Int),
               }),
               arguments: vec![CppFunctionArgument {
                                 name: "x".to_string(),
                                 argument_type: CppType {
                                   indirection: CppTypeIndirection::None,
                                   is_const: false,
                                   base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Int),
                                 },
                                 has_default_value: false,
                               }],
               allows_variadic_arguments: false,
               include_file: "myfakelib.h".to_string(),
               origin_location: None,
               template_arguments: None,
             });
}

fn simple_func_with_default_value() {
  let data = run_parser("bool func1(int x = 42);");
  assert!(data.template_instantiations.is_empty());
  assert!(data.types.is_empty());
  assert!(data.methods.len() == 1);
  assert_eq!(data.methods[0],
             CppMethod {
               name: "func1".to_string(),
               class_membership: None,
               operator: None,
               return_type: Some(CppType {
                 indirection: CppTypeIndirection::None,
                 is_const: false,
                 base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Bool),
               }),
               arguments: vec![CppFunctionArgument {
                                 name: "x".to_string(),
                                 argument_type: CppType {
                                   indirection: CppTypeIndirection::None,
                                   is_const: false,
                                   base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Int),
                                 },
                                 has_default_value: true,
                               }],
               allows_variadic_arguments: false,
               include_file: "myfakelib.h".to_string(),
               origin_location: None,
               template_arguments: None,
             });
}

fn functions_with_class_arg() {
  let data = run_parser("class Magic { public: int a, b; };
  bool func1(Magic x);
  bool func1(Magic* x);
  bool func2(const Magic&);");
  assert!(data.template_instantiations.is_empty());
  assert_eq!(data.types.len(), 1);
  assert_eq!(data.types[0].name, "Magic");
  match data.types[0].kind {
    CppTypeKind::Class { ref size, ref bases, ref fields, ref template_arguments } => {
      assert!(size.is_some());
      assert!(template_arguments.is_none());
      assert!(bases.is_empty());
      assert_eq!(fields,
                 &vec![CppClassField {
                         name: "a".to_string(),
                         field_type: CppType {
                           indirection: CppTypeIndirection::None,
                           is_const: false,
                           base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Int),
                         },
                         visibility: CppVisibility::Public,
                       },
                       CppClassField {
                         name: "b".to_string(),
                         field_type: CppType {
                           indirection: CppTypeIndirection::None,
                           is_const: false,
                           base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Int),
                         },
                         visibility: CppVisibility::Public,
                       }]);

    }
    _ => panic!("invalid type kind"),
  }
  assert!(data.methods.len() == 3);
  assert_eq!(data.methods[0],
             CppMethod {
               name: "func1".to_string(),
               class_membership: None,
               operator: None,
               return_type: Some(CppType {
                 indirection: CppTypeIndirection::None,
                 is_const: false,
                 base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Bool),
               }),
               arguments: vec![CppFunctionArgument {
                                 name: "x".to_string(),
                                 argument_type: CppType {
                                   indirection: CppTypeIndirection::None,
                                   is_const: false,
                                   base: CppTypeBase::Class {
                                     name: "Magic".to_string(),
                                     template_arguments: None,
                                   },
                                 },
                                 has_default_value: false,
                               }],
               allows_variadic_arguments: false,
               include_file: "myfakelib.h".to_string(),
               origin_location: None,
               template_arguments: None,
             });
  assert_eq!(data.methods[1],
             CppMethod {
               name: "func1".to_string(),
               class_membership: None,
               operator: None,
               return_type: Some(CppType {
                 indirection: CppTypeIndirection::None,
                 is_const: false,
                 base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Bool),
               }),
               arguments: vec![CppFunctionArgument {
                                 name: "x".to_string(),
                                 argument_type: CppType {
                                   indirection: CppTypeIndirection::Ptr,
                                   is_const: false,
                                   base: CppTypeBase::Class {
                                     name: "Magic".to_string(),
                                     template_arguments: None,
                                   },
                                 },
                                 has_default_value: false,
                               }],
               allows_variadic_arguments: false,
               include_file: "myfakelib.h".to_string(),
               origin_location: None,
               template_arguments: None,
             });
  assert_eq!(data.methods[2],
             CppMethod {
               name: "func2".to_string(),
               class_membership: None,
               operator: None,
               return_type: Some(CppType {
                 indirection: CppTypeIndirection::None,
                 is_const: false,
                 base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Bool),
               }),
               arguments: vec![CppFunctionArgument {
                                 name: "arg1".to_string(),
                                 argument_type: CppType {
                                   indirection: CppTypeIndirection::Ref,
                                   is_const: true,
                                   base: CppTypeBase::Class {
                                     name: "Magic".to_string(),
                                     template_arguments: None,
                                   },
                                 },
                                 has_default_value: false,
                               }],
               allows_variadic_arguments: false,
               include_file: "myfakelib.h".to_string(),
               origin_location: None,
               template_arguments: None,
             });
}

fn func_with_unknown_type() {
  let data = run_parser("class SomeClass; \n int func1(SomeClass* x);");
  assert!(data.template_instantiations.is_empty());
  assert!(data.types.is_empty());
  assert!(data.methods.is_empty());
}

fn variadic_func() {
  let data = run_parser("int my_printf ( const char * format, ... );");
  assert!(data.template_instantiations.is_empty());
  assert!(data.types.is_empty());
  assert!(data.methods.len() == 1);
  assert_eq!(data.methods[0],
             CppMethod {
               name: "my_printf".to_string(),
               class_membership: None,
               operator: None,
               return_type: Some(CppType {
                 indirection: CppTypeIndirection::None,
                 is_const: false,
                 base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Int),
               }),
               arguments: vec![CppFunctionArgument {
                                 name: "format".to_string(),
                                 argument_type: CppType {
                                   indirection: CppTypeIndirection::Ptr,
                                   is_const: true,
                                   base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Char),
                                 },
                                 has_default_value: false,
                               }],
               allows_variadic_arguments: true,
               include_file: "myfakelib.h".to_string(),
               origin_location: None,
               template_arguments: None,
             });
}

fn free_template_func() {
  let data = run_parser("template<typename T> T abs(T value) { return 2*value; }");
  assert!(data.template_instantiations.is_empty());
  assert!(data.types.is_empty());
  assert!(data.methods.len() == 1);
  assert_eq!(data.methods[0],
             CppMethod {
               name: "abs".to_string(),
               class_membership: None,
               operator: None,
               return_type: Some(CppType {
                 indirection: CppTypeIndirection::None,
                 is_const: false,
                 base: CppTypeBase::TemplateParameter {
                   nested_level: 0,
                   index: 0,
                 },
               }),
               arguments: vec![CppFunctionArgument {
                                 name: "value".to_string(),
                                 argument_type: CppType {
                                   indirection: CppTypeIndirection::None,
                                   is_const: false,
                                   base: CppTypeBase::TemplateParameter {
                                     nested_level: 0,
                                     index: 0,
                                   },
                                 },
                                 has_default_value: false,
                               }],
               allows_variadic_arguments: false,
               include_file: "myfakelib.h".to_string(),
               origin_location: None,
               template_arguments: Some(vec!["T".to_string()]),
             });
}

fn free_func_operator_sub() {
  for code in &["class C1 {}; \n C1 operator-(C1 a, C1 b);",
                "class C1 {}; \n C1 operator -(C1 a, C1 b);"] {
    let data = run_parser(code);
    assert!(data.template_instantiations.is_empty());
    assert!(data.types.len() == 1);
    assert!(data.methods.len() == 1);
    assert_eq!(data.methods[0],
               CppMethod {
                 name: "operator-".to_string(),
                 class_membership: None,
                 operator: Some(CppOperator::Subtraction),
                 return_type: Some(CppType {
                   indirection: CppTypeIndirection::None,
                   is_const: false,
                   base: CppTypeBase::Class {
                     name: "C1".to_string(),
                     template_arguments: None,
                   },
                 }),
                 arguments: vec![CppFunctionArgument {
                                   name: "a".to_string(),
                                   argument_type: CppType {
                                     indirection: CppTypeIndirection::None,
                                     is_const: false,
                                     base: CppTypeBase::Class {
                                       name: "C1".to_string(),
                                       template_arguments: None,
                                     },
                                   },
                                   has_default_value: false,
                                 },
                                 CppFunctionArgument {
                                   name: "b".to_string(),
                                   argument_type: CppType {
                                     indirection: CppTypeIndirection::None,
                                     is_const: false,
                                     base: CppTypeBase::Class {
                                       name: "C1".to_string(),
                                       template_arguments: None,
                                     },
                                   },
                                   has_default_value: false,
                                 }],
                 allows_variadic_arguments: false,
                 include_file: "myfakelib.h".to_string(),
                 origin_location: None,
                 template_arguments: None,
               });
  }
}

// fn operator_bool() {
// let data = run_parser("class C1 { operator bool(const C1& a); };");
// assert!(data.template_instantiations.is_empty());
// assert!(data.types.len() == 1);
// assert!(data.methods.len() == 1);
// assert_eq!(data.methods[0],
// CppMethod {
// name: "operator bool".to_string(),
// class_membership: None,
// operator: Some(CppOperator::Conversion(CppType {
// indirection: CppTypeIndirection::None,
// is_const: false,
// base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Bool),
// })),
// return_type: Some(CppType {
// indirection: CppTypeIndirection::None,
// is_const: false,
// base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Bool),
// }),
// arguments: vec![CppFunctionArgument {
// name: "a".to_string(),
// argument_type: CppType {
// indirection: CppTypeIndirection::Ref,
// is_const: true,
// base: CppTypeBase::Class {
// name: "C1".to_string(),
// template_arguments: None,
// },
// },
// has_default_value: false,
// }],
// allows_variadic_arguments: false,
// include_file: "myfakelib.h".to_string(),
// origin_location: None,
// template_arguments: None,
// });
// }

#[test]
fn tests() {
  // clang can't be used from multiple threads, so these checks
  // must be run consequently
  simple_func();
  simple_func_with_default_value();
  functions_with_class_arg();
  func_with_unknown_type();
  variadic_func();
  free_template_func();
  free_func_operator_sub();
  // free_func_operator_bool();
}
<commit_msg>more parser tests<commit_after>
// make sure to add test calls to tests() func
#![forbid(dead_code)]

extern crate tempdir;

use cpp_parser;
use cpp_data::*;
use utils::PathBufPushTweak;
use std::fs;
use std::fs::File;
use std::io::Write;
use cpp_method::*;
use cpp_type::*;
use cpp_operator::CppOperator;

fn run_parser(code: &'static str) -> CppData {
  let dir = tempdir::TempDir::new("test_cpp_parser_run").unwrap();
  let include_dir = dir.path().with_added("include");
  fs::create_dir(&include_dir).unwrap();
  let include_name = "myfakelib.h";
  let include_file_path = include_dir.with_added(&include_name);
  {
    let mut include_file = File::create(&include_file_path).unwrap();
    include_file.write(code.as_bytes()).unwrap();
    include_file.write("\n".as_bytes()).unwrap();
  }
  let mut result = cpp_parser::run(cpp_parser::CppParserConfig {
    include_dirs: vec![include_dir],
    header_name: include_name.to_string(),
    tmp_cpp_path: dir.path().with_added("1.cpp"),
    name_blacklist: Vec::new(),
  });
  for method in &mut result.methods {
    if let Some(ref mut origin_location) = method.origin_location {
      assert_eq!(origin_location.include_file_path,
                 include_file_path.display().to_string());
      assert!(origin_location.line > 0);
    } else {
      panic!("no origin_location in pare result");
    }
    method.origin_location = None;
  }
  result
}

fn simple_func() {
  let data = run_parser("int func1(int x);");
  assert!(data.template_instantiations.is_empty());
  assert!(data.types.is_empty());
  assert!(data.methods.len() == 1);
  assert_eq!(data.methods[0],
             CppMethod {
               name: "func1".to_string(),
               class_membership: None,
               operator: None,
               return_type: Some(CppType {
                 indirection: CppTypeIndirection::None,
                 is_const: false,
                 base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Int),
               }),
               arguments: vec![CppFunctionArgument {
                                 name: "x".to_string(),
                                 argument_type: CppType {
                                   indirection: CppTypeIndirection::None,
                                   is_const: false,
                                   base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Int),
                                 },
                                 has_default_value: false,
                               }],
               allows_variadic_arguments: false,
               include_file: "myfakelib.h".to_string(),
               origin_location: None,
               template_arguments: None,
             });
}

fn simple_func_with_default_value() {
  let data = run_parser("bool func1(int x = 42);");
  assert!(data.template_instantiations.is_empty());
  assert!(data.types.is_empty());
  assert!(data.methods.len() == 1);
  assert_eq!(data.methods[0],
             CppMethod {
               name: "func1".to_string(),
               class_membership: None,
               operator: None,
               return_type: Some(CppType {
                 indirection: CppTypeIndirection::None,
                 is_const: false,
                 base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Bool),
               }),
               arguments: vec![CppFunctionArgument {
                                 name: "x".to_string(),
                                 argument_type: CppType {
                                   indirection: CppTypeIndirection::None,
                                   is_const: false,
                                   base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Int),
                                 },
                                 has_default_value: true,
                               }],
               allows_variadic_arguments: false,
               include_file: "myfakelib.h".to_string(),
               origin_location: None,
               template_arguments: None,
             });
}

fn functions_with_class_arg() {
  let data = run_parser("class Magic { public: int a, b; };
  bool func1(Magic x);
  bool func1(Magic* x);
  bool func2(const Magic&);");
  assert!(data.template_instantiations.is_empty());
  assert_eq!(data.types.len(), 1);
  assert_eq!(data.types[0].name, "Magic");
  match data.types[0].kind {
    CppTypeKind::Class { ref size, ref bases, ref fields, ref template_arguments } => {
      assert!(size.is_some());
      assert!(template_arguments.is_none());
      assert!(bases.is_empty());
      assert_eq!(fields,
                 &vec![CppClassField {
                         name: "a".to_string(),
                         field_type: CppType {
                           indirection: CppTypeIndirection::None,
                           is_const: false,
                           base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Int),
                         },
                         visibility: CppVisibility::Public,
                       },
                       CppClassField {
                         name: "b".to_string(),
                         field_type: CppType {
                           indirection: CppTypeIndirection::None,
                           is_const: false,
                           base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Int),
                         },
                         visibility: CppVisibility::Public,
                       }]);

    }
    _ => panic!("invalid type kind"),
  }
  assert!(data.methods.len() == 3);
  assert_eq!(data.methods[0],
             CppMethod {
               name: "func1".to_string(),
               class_membership: None,
               operator: None,
               return_type: Some(CppType {
                 indirection: CppTypeIndirection::None,
                 is_const: false,
                 base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Bool),
               }),
               arguments: vec![CppFunctionArgument {
                                 name: "x".to_string(),
                                 argument_type: CppType {
                                   indirection: CppTypeIndirection::None,
                                   is_const: false,
                                   base: CppTypeBase::Class {
                                     name: "Magic".to_string(),
                                     template_arguments: None,
                                   },
                                 },
                                 has_default_value: false,
                               }],
               allows_variadic_arguments: false,
               include_file: "myfakelib.h".to_string(),
               origin_location: None,
               template_arguments: None,
             });
  assert_eq!(data.methods[1],
             CppMethod {
               name: "func1".to_string(),
               class_membership: None,
               operator: None,
               return_type: Some(CppType {
                 indirection: CppTypeIndirection::None,
                 is_const: false,
                 base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Bool),
               }),
               arguments: vec![CppFunctionArgument {
                                 name: "x".to_string(),
                                 argument_type: CppType {
                                   indirection: CppTypeIndirection::Ptr,
                                   is_const: false,
                                   base: CppTypeBase::Class {
                                     name: "Magic".to_string(),
                                     template_arguments: None,
                                   },
                                 },
                                 has_default_value: false,
                               }],
               allows_variadic_arguments: false,
               include_file: "myfakelib.h".to_string(),
               origin_location: None,
               template_arguments: None,
             });
  assert_eq!(data.methods[2],
             CppMethod {
               name: "func2".to_string(),
               class_membership: None,
               operator: None,
               return_type: Some(CppType {
                 indirection: CppTypeIndirection::None,
                 is_const: false,
                 base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Bool),
               }),
               arguments: vec![CppFunctionArgument {
                                 name: "arg1".to_string(),
                                 argument_type: CppType {
                                   indirection: CppTypeIndirection::Ref,
                                   is_const: true,
                                   base: CppTypeBase::Class {
                                     name: "Magic".to_string(),
                                     template_arguments: None,
                                   },
                                 },
                                 has_default_value: false,
                               }],
               allows_variadic_arguments: false,
               include_file: "myfakelib.h".to_string(),
               origin_location: None,
               template_arguments: None,
             });
}

fn func_with_unknown_type() {
  let data = run_parser("class SomeClass; \n int func1(SomeClass* x);");
  assert!(data.template_instantiations.is_empty());
  assert!(data.types.is_empty());
  assert!(data.methods.is_empty());
}

fn variadic_func() {
  let data = run_parser("int my_printf ( const char * format, ... );");
  assert!(data.template_instantiations.is_empty());
  assert!(data.types.is_empty());
  assert!(data.methods.len() == 1);
  assert_eq!(data.methods[0],
             CppMethod {
               name: "my_printf".to_string(),
               class_membership: None,
               operator: None,
               return_type: Some(CppType {
                 indirection: CppTypeIndirection::None,
                 is_const: false,
                 base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Int),
               }),
               arguments: vec![CppFunctionArgument {
                                 name: "format".to_string(),
                                 argument_type: CppType {
                                   indirection: CppTypeIndirection::Ptr,
                                   is_const: true,
                                   base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Char),
                                 },
                                 has_default_value: false,
                               }],
               allows_variadic_arguments: true,
               include_file: "myfakelib.h".to_string(),
               origin_location: None,
               template_arguments: None,
             });
}

fn free_template_func() {
  let data = run_parser("template<typename T> T abs(T value) { return 2*value; }");
  assert!(data.template_instantiations.is_empty());
  assert!(data.types.is_empty());
  assert!(data.methods.len() == 1);
  assert_eq!(data.methods[0],
             CppMethod {
               name: "abs".to_string(),
               class_membership: None,
               operator: None,
               return_type: Some(CppType {
                 indirection: CppTypeIndirection::None,
                 is_const: false,
                 base: CppTypeBase::TemplateParameter {
                   nested_level: 0,
                   index: 0,
                 },
               }),
               arguments: vec![CppFunctionArgument {
                                 name: "value".to_string(),
                                 argument_type: CppType {
                                   indirection: CppTypeIndirection::None,
                                   is_const: false,
                                   base: CppTypeBase::TemplateParameter {
                                     nested_level: 0,
                                     index: 0,
                                   },
                                 },
                                 has_default_value: false,
                               }],
               allows_variadic_arguments: false,
               include_file: "myfakelib.h".to_string(),
               origin_location: None,
               template_arguments: Some(vec!["T".to_string()]),
             });
}

fn free_func_operator_sub() {
  for code in &["class C1 {}; \n C1 operator-(C1 a, C1 b);",
                "class C1 {}; \n C1 operator -(C1 a, C1 b);"] {
    let data = run_parser(code);
    assert!(data.template_instantiations.is_empty());
    assert!(data.types.len() == 1);
    assert!(data.methods.len() == 1);
    assert_eq!(data.methods[0],
               CppMethod {
                 name: "operator-".to_string(),
                 class_membership: None,
                 operator: Some(CppOperator::Subtraction),
                 return_type: Some(CppType {
                   indirection: CppTypeIndirection::None,
                   is_const: false,
                   base: CppTypeBase::Class {
                     name: "C1".to_string(),
                     template_arguments: None,
                   },
                 }),
                 arguments: vec![CppFunctionArgument {
                                   name: "a".to_string(),
                                   argument_type: CppType {
                                     indirection: CppTypeIndirection::None,
                                     is_const: false,
                                     base: CppTypeBase::Class {
                                       name: "C1".to_string(),
                                       template_arguments: None,
                                     },
                                   },
                                   has_default_value: false,
                                 },
                                 CppFunctionArgument {
                                   name: "b".to_string(),
                                   argument_type: CppType {
                                     indirection: CppTypeIndirection::None,
                                     is_const: false,
                                     base: CppTypeBase::Class {
                                       name: "C1".to_string(),
                                       template_arguments: None,
                                     },
                                   },
                                   has_default_value: false,
                                 }],
                 allows_variadic_arguments: false,
                 include_file: "myfakelib.h".to_string(),
                 origin_location: None,
                 template_arguments: None,
               });
  }
}

// fn operator_bool() {
// let data = run_parser("class C1 { operator bool(const C1& a); };");
// assert!(data.template_instantiations.is_empty());
// assert!(data.types.len() == 1);
// assert!(data.methods.len() == 1);
// assert_eq!(data.methods[0],
// CppMethod {
// name: "operator bool".to_string(),
// class_membership: None,
// operator: Some(CppOperator::Conversion(CppType {
// indirection: CppTypeIndirection::None,
// is_const: false,
// base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Bool),
// })),
// return_type: Some(CppType {
// indirection: CppTypeIndirection::None,
// is_const: false,
// base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Bool),
// }),
// arguments: vec![CppFunctionArgument {
// name: "a".to_string(),
// argument_type: CppType {
// indirection: CppTypeIndirection::Ref,
// is_const: true,
// base: CppTypeBase::Class {
// name: "C1".to_string(),
// template_arguments: None,
// },
// },
// has_default_value: false,
// }],
// allows_variadic_arguments: false,
// include_file: "myfakelib.h".to_string(),
// origin_location: None,
// template_arguments: None,
// });
// }

fn simple_class_method() {
  let data = run_parser("class MyClass {
    public:
      int func1(int x);
    private:
      int m_x;
    };");
  assert!(data.template_instantiations.is_empty());
  assert!(data.types.len() == 1);
  assert_eq!(data.types[0].name, "MyClass");
  match data.types[0].kind {
    CppTypeKind::Class { ref size, ref bases, ref fields, ref template_arguments } => {
      assert!(size.is_some());
      assert!(template_arguments.is_none());
      assert!(bases.is_empty());
      assert!(fields.len() == 1);
    }
    _ => panic!("invalid type kind"),
  }
  assert!(data.methods.len() == 1);
  assert_eq!(data.methods[0],
             CppMethod {
               name: "func1".to_string(),
               class_membership: Some(CppMethodClassMembership {
                 class_type: CppTypeBase::Class {
                   name: "MyClass".to_string(),
                   template_arguments: None,
                 },
                 kind: CppMethodKind::Regular,
                 is_virtual: false,
                 is_pure_virtual: false,
                 is_const: false,
                 is_static: false,
                 visibility: CppVisibility::Public,
                 is_signal: false,
               }),
               operator: None,
               return_type: Some(CppType {
                 indirection: CppTypeIndirection::None,
                 is_const: false,
                 base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Int),
               }),
               arguments: vec![CppFunctionArgument {
                                 name: "x".to_string(),
                                 argument_type: CppType {
                                   indirection: CppTypeIndirection::None,
                                   is_const: false,
                                   base: CppTypeBase::BuiltInNumeric(CppBuiltInNumericType::Int),
                                 },
                                 has_default_value: false,
                               }],
               allows_variadic_arguments: false,
               include_file: "myfakelib.h".to_string(),
               origin_location: None,
               template_arguments: None,
             });
}

fn advanced_class_methods() {
  let data = run_parser("class MyClass {
    public:
      MyClass(bool a, bool b, bool c);
      virtual ~MyClass();
      static int func1(int x);
    };");
  assert_eq!(data.methods.len(), 3);
  assert_eq!(data.methods[0].name, "MyClass");
  assert!(data.methods[0].class_membership.as_ref().unwrap().kind.is_constructor());
  assert_eq!(data.methods[0].arguments.len(), 3);
  assert_eq!(data.methods[0].return_type, Some(CppType::void()));

  assert_eq!(data.methods[1].name, "~MyClass");
  assert!(data.methods[1].class_membership.as_ref().unwrap().kind.is_destructor());
  assert_eq!(data.methods[1].arguments.len(), 0);
  assert_eq!(data.methods[1].return_type, Some(CppType::void()));

  assert_eq!(data.methods[2].name, "func1");
  assert!(data.methods[2].class_membership.as_ref().unwrap().is_static);

}

#[test]
fn tests() {
  // clang can't be used from multiple threads, so these checks
  // must be run consequently
  simple_func();
  simple_func_with_default_value();
  functions_with_class_arg();
  func_with_unknown_type();
  variadic_func();
  free_template_func();
  free_func_operator_sub();
  simple_class_method();
  advanced_class_methods();
  // free_func_operator_bool();
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#![crate_name = "devtools"]
#![crate_type = "rlib"]

#![comment = "The Servo Parallel Browser Project"]
#![license = "MPL"]

#![allow(non_snake_case)]

#![feature(phase)]

#![feature(phase)]
#[phase(plugin, link)]
extern crate log;

/// An actor-based remote devtools server implementation. Only tested with nightly Firefox
/// versions at time of writing. Largely based on reverse-engineering of Firefox chrome
/// devtool logs and reading of [code](http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/).

extern crate collections;
extern crate core;
extern crate devtools_traits;
extern crate serialize;
extern crate sync;
extern crate "msg" as servo_msg;
extern crate "util" as servo_util;

use actor::{Actor, ActorRegistry};
use actors::console::ConsoleActor;
use actors::inspector::InspectorActor;
use actors::root::RootActor;
use actors::tab::TabActor;
use protocol::JsonPacketStream;

use devtools_traits::{ServerExitMsg, DevtoolsControlMsg, NewGlobal, DevtoolScriptControlMsg};
use servo_msg::constellation_msg::PipelineId;
use servo_util::task::spawn_named;

use std::cell::RefCell;
use std::collections::HashMap;
use std::comm;
use std::comm::{Disconnected, Empty};
use std::io::{TcpListener, TcpStream};
use std::io::{Acceptor, Listener, TimedOut};
use sync::{Arc, Mutex};

mod actor;
/// Corresponds to http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/
mod actors {
    pub mod console;
    pub mod inspector;
    pub mod root;
    pub mod tab;
}
mod protocol;

/// Spin up a devtools server that listens for connections on the specified port.
pub fn start_server(port: u16) -> Sender<DevtoolsControlMsg> {
    let (sender, receiver) = comm::channel();
    spawn_named("Devtools", proc() {
        run_server(receiver, port)
    });
    sender
}

static POLL_TIMEOUT: u64 = 300;

fn run_server(receiver: Receiver<DevtoolsControlMsg>, port: u16) {
    let listener = TcpListener::bind(format!("{}:{}", "127.0.0.1", port).as_slice());

    // bind the listener to the specified address
    let mut acceptor = listener.listen().unwrap();
    acceptor.set_timeout(Some(POLL_TIMEOUT));

    let mut registry = ActorRegistry::new();

    let root = box RootActor {
        tabs: vec!(),
    };

    registry.register(root);
    registry.find::<RootActor>("root");

    let actors = Arc::new(Mutex::new(registry));

    let mut accepted_connections: Vec<TcpStream> = Vec::new();

    let mut actor_pipelines: HashMap<PipelineId, String> = HashMap::new();

    /// Process the input from a single devtools client until EOF.
    fn handle_client(actors: Arc<Mutex<ActorRegistry>>, mut stream: TcpStream) {
        println!("connection established to {}", stream.peer_name().unwrap());
        {
            let actors = actors.lock();
            let msg = actors.find::<RootActor>("root").encodable();
            stream.write_json_packet(&msg);
        }

        'outer: loop {
            match stream.read_json_packet() {
                Ok(json_packet) => {
                    match actors.lock().handle_message(json_packet.as_object().unwrap(),
                                                       &mut stream) {
                        Ok(()) => {},
                        Err(()) => {
                            println!("error: devtools actor stopped responding");
                            stream.close_read();
                            stream.close_write();
                            break 'outer
                        }
                    }
                }
                Err(e) => {
                    println!("error: {}", e.desc);
                    break 'outer
                }
            }
        }
    }

    // We need separate actor representations for each script global that exists;
    // clients can theoretically connect to multiple globals simultaneously.
    // TODO: move this into the root or tab modules?
    fn handle_new_global(actors: Arc<Mutex<ActorRegistry>>,
                         pipeline: PipelineId,
                         sender: Sender<DevtoolScriptControlMsg>,
                         actor_pipelines: &mut HashMap<PipelineId, String>) {
        let mut actors = actors.lock();

        //TODO: move all this actor creation into a constructor method on TabActor
        let (tab, console, inspector) = {
            let console = ConsoleActor {
                name: actors.new_name("console"),
                script_chan: sender.clone(),
                pipeline: pipeline,
                streams: RefCell::new(Vec::new()),
            };
            let inspector = InspectorActor {
                name: actors.new_name("inspector"),
                walker: RefCell::new(None),
                pageStyle: RefCell::new(None),
                highlighter: RefCell::new(None),
                script_chan: sender,
                pipeline: pipeline,
            };
            //TODO: send along the current page title and URL
            let tab = TabActor {
                name: actors.new_name("tab"),
                title: "".to_string(),
                url: "about:blank".to_string(),
                console: console.name(),
                inspector: inspector.name(),
            };

            let root = actors.find_mut::<RootActor>("root");
            root.tabs.push(tab.name.clone());
            (tab, console, inspector)
        };

        actor_pipelines.insert(pipeline, tab.name.clone());
        actors.register(box tab);
        actors.register(box console);
        actors.register(box inspector);
    }

    //TODO: figure out some system that allows us to watch for new connections,
    //      shut down existing ones at arbitrary times, and also watch for messages
    //      from multiple script tasks simultaneously. Polling for new connections
    //      for 300ms and then checking the receiver is not a good compromise
    //      (and makes Servo hang on exit if there's an open connection, no less).
    // accept connections and process them, spawning a new tasks for each one
    loop {
        match acceptor.accept() {
            Err(ref e) if e.kind == TimedOut => {
                match receiver.try_recv() {
                    Ok(ServerExitMsg) | Err(Disconnected) => break,
                    Ok(NewGlobal(id, sender)) => handle_new_global(actors.clone(), id, sender, &mut actor_pipelines),
                    Err(Empty) => acceptor.set_timeout(Some(POLL_TIMEOUT)),
                }
            }
            Err(_e) => { /* connection failed */ }
            Ok(stream) => {
                let actors = actors.clone();
                accepted_connections.push(stream.clone());
                spawn_named("DevtoolsClientHandler", proc() {
                    // connection succeeded
                    handle_client(actors, stream.clone())
                })
            }
        }
    }

    for connection in accepted_connections.iter_mut() {
        let _read = connection.close_read();
        let _write = connection.close_write();
    }
}
<commit_msg>Fixes #4232<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#![crate_name = "devtools"]
#![crate_type = "rlib"]

#![comment = "The Servo Parallel Browser Project"]
#![license = "MPL"]

#![allow(non_snake_case)]

#![feature(phase)]

#![feature(phase)]
#[phase(plugin, link)]
extern crate log;

/// An actor-based remote devtools server implementation. Only tested with nightly Firefox
/// versions at time of writing. Largely based on reverse-engineering of Firefox chrome
/// devtool logs and reading of [code](http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/).

extern crate collections;
extern crate core;
extern crate devtools_traits;
extern crate serialize;
extern crate sync;
extern crate "msg" as servo_msg;
extern crate "util" as servo_util;

use actor::{Actor, ActorRegistry};
use actors::console::ConsoleActor;
use actors::inspector::InspectorActor;
use actors::root::RootActor;
use actors::tab::TabActor;
use protocol::JsonPacketStream;

use devtools_traits::{ServerExitMsg, DevtoolsControlMsg, NewGlobal, DevtoolScriptControlMsg};
use servo_msg::constellation_msg::PipelineId;
use servo_util::task::spawn_named;

use std::cell::RefCell;
use std::collections::HashMap;
use std::comm;
use std::comm::{Disconnected, Empty};
use std::io::{TcpListener, TcpStream};
use std::io::{Acceptor, Listener, TimedOut};
use sync::{Arc, Mutex};

mod actor;
/// Corresponds to http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/
mod actors {
    pub mod console;
    pub mod inspector;
    pub mod root;
    pub mod tab;
}
mod protocol;

/// Spin up a devtools server that listens for connections on the specified port.
pub fn start_server(port: u16) -> Sender<DevtoolsControlMsg> {
    let (sender, receiver) = comm::channel();
    spawn_named("Devtools", proc() {
        run_server(receiver, port)
    });
    sender
}

static POLL_TIMEOUT: u64 = 300;

fn run_server(receiver: Receiver<DevtoolsControlMsg>, port: u16) {
    let listener = TcpListener::bind(format!("{}:{}", "127.0.0.1", port).as_slice());

    // bind the listener to the specified address
    let mut acceptor = listener.listen().unwrap();
    acceptor.set_timeout(Some(POLL_TIMEOUT));

    let mut registry = ActorRegistry::new();

    let root = box RootActor {
        tabs: vec!(),
    };

    registry.register(root);
    registry.find::<RootActor>("root");

    let actors = Arc::new(Mutex::new(registry));

    let mut accepted_connections: Vec<TcpStream> = Vec::new();

    let mut actor_pipelines: HashMap<PipelineId, String> = HashMap::new();

    /// Process the input from a single devtools client until EOF.
    fn handle_client(actors: Arc<Mutex<ActorRegistry>>, mut stream: TcpStream) {
        println!("connection established to {}", stream.peer_name().unwrap());
        {
            let actors = actors.lock();
            let msg = actors.find::<RootActor>("root").encodable();
            stream.write_json_packet(&msg);
        }

        'outer: loop {
            match stream.read_json_packet() {
                Ok(json_packet) => {
                    match actors.lock().handle_message(json_packet.as_object().unwrap(),
                                                       &mut stream) {
                        Ok(()) => {},
                        Err(()) => {
                            println!("error: devtools actor stopped responding");
                            let _ = stream.close_read();
                            let _ = stream.close_write();
                            break 'outer
                        }
                    }
                }
                Err(e) => {
                    println!("error: {}", e.desc);
                    break 'outer
                }
            }
        }
    }

    // We need separate actor representations for each script global that exists;
    // clients can theoretically connect to multiple globals simultaneously.
    // TODO: move this into the root or tab modules?
    fn handle_new_global(actors: Arc<Mutex<ActorRegistry>>,
                         pipeline: PipelineId,
                         sender: Sender<DevtoolScriptControlMsg>,
                         actor_pipelines: &mut HashMap<PipelineId, String>) {
        let mut actors = actors.lock();

        //TODO: move all this actor creation into a constructor method on TabActor
        let (tab, console, inspector) = {
            let console = ConsoleActor {
                name: actors.new_name("console"),
                script_chan: sender.clone(),
                pipeline: pipeline,
                streams: RefCell::new(Vec::new()),
            };
            let inspector = InspectorActor {
                name: actors.new_name("inspector"),
                walker: RefCell::new(None),
                pageStyle: RefCell::new(None),
                highlighter: RefCell::new(None),
                script_chan: sender,
                pipeline: pipeline,
            };
            //TODO: send along the current page title and URL
            let tab = TabActor {
                name: actors.new_name("tab"),
                title: "".to_string(),
                url: "about:blank".to_string(),
                console: console.name(),
                inspector: inspector.name(),
            };

            let root = actors.find_mut::<RootActor>("root");
            root.tabs.push(tab.name.clone());
            (tab, console, inspector)
        };

        actor_pipelines.insert(pipeline, tab.name.clone());
        actors.register(box tab);
        actors.register(box console);
        actors.register(box inspector);
    }

    //TODO: figure out some system that allows us to watch for new connections,
    //      shut down existing ones at arbitrary times, and also watch for messages
    //      from multiple script tasks simultaneously. Polling for new connections
    //      for 300ms and then checking the receiver is not a good compromise
    //      (and makes Servo hang on exit if there's an open connection, no less).
    // accept connections and process them, spawning a new tasks for each one
    loop {
        match acceptor.accept() {
            Err(ref e) if e.kind == TimedOut => {
                match receiver.try_recv() {
                    Ok(ServerExitMsg) | Err(Disconnected) => break,
                    Ok(NewGlobal(id, sender)) => handle_new_global(actors.clone(), id, sender, &mut actor_pipelines),
                    Err(Empty) => acceptor.set_timeout(Some(POLL_TIMEOUT)),
                }
            }
            Err(_e) => { /* connection failed */ }
            Ok(stream) => {
                let actors = actors.clone();
                accepted_connections.push(stream.clone());
                spawn_named("DevtoolsClientHandler", proc() {
                    // connection succeeded
                    handle_client(actors, stream.clone())
                })
            }
        }
    }

    for connection in accepted_connections.iter_mut() {
        let _read = connection.close_read();
        let _write = connection.close_write();
    }
}
<|endoftext|>