<commit_before>pub mod ffi;
pub mod frame;

use std::mem;
use std::ptr;
use std::ffi::CString;

use error::{RuntimeError, ErrorKind, VoidResult};

use super::utils::fn_ptr::FnPtrLoader;

#[inline]
pub fn load<T: FnPtrLoader>(loader: &T) {
   unsafe {
      ffi::load_functions(loader)
   };
}

#[inline]
fn gl_error<T>(message: &str) -> Result<T, RuntimeError> {
   Err(
      RuntimeError::new(
         ErrorKind::GL,
         message.to_string()
      )
   )
}

#[inline]
fn gl_result<T>(value: T) -> Result<T, RuntimeError> {
   let result = unsafe {
      ffi::glGetError()
   };

   match result {
      ffi::GL_NO_ERROR => Ok(value),
      ffi::GL_INVALID_ENUM => gl_error(
         "An unacceptable value is specified for an enumerated argument"
      ),
      ffi::GL_INVALID_VALUE => gl_error(
         "A numeric argument is out of range"
      ),
      ffi::GL_INVALID_OPERATION => gl_error(
         "The specified operation is not allowed in the current state"
      ),
      ffi::GL_INVALID_FRAMEBUFFER_OPERATION => gl_error(
         "The framebuffer object is not complete"
      ),
      ffi::GL_OUT_OF_MEMORY => gl_error(
         "There is not enough memory left to execute the command"
      ),
      ffi::GL_STACK_UNDERFLOW => gl_error(
         "Performing an operation that would cause an internal stack to underflow"
      ),
      ffi::GL_STACK_OVERFLOW => gl_error(
         "Performing an operation that would cause an internal stack to overflow"
      ),
      _ => gl_error("Unknown OpenGL error")
   }
}

#[inline]
pub fn reset_pixelstore_alignment() -> VoidResult {
   unsafe {
      ffi::glPixelStorei(ffi::GL_UNPACK_ALIGNMENT, 1);
   }

   gl_result(())
}

#[inline]
pub fn enable_framebuffer_srgb() -> VoidResult {
   unsafe {
      ffi::glEnable(ffi::GL_FRAMEBUFFER_SRGB);
   }

   gl_result(())
}

#[inline]
pub fn viewport(width: u32, height: u32) -> VoidResult {
   unsafe {
      ffi::glViewport(0, 0, width as ffi::GLsizei, height as ffi::GLsizei)
   }

   gl_result(())
}

#[inline]
pub fn vertex_attrib_pointer(
   index: ffi::GLuint, size: ffi::GLint, buffer: &[ffi::GLfloat]
) -> VoidResult {

   unsafe {
      ffi::glVertexAttribPointer(
         index,
         size,
         ffi::GL_FLOAT,
         ffi::GL_FALSE,
         0,
         buffer.as_ptr() as *const ffi::GLvoid
      )
   }

   gl_result(())
}

#[inline]
pub fn enable_vertex_attrib_array(index: ffi::GLuint) -> VoidResult {
   unsafe {
      ffi::glEnableVertexAttribArray(index)
   }

   gl_result(())
}

#[inline]
pub fn uniform_value_1i(location: ffi::GLint, value: ffi::GLint) -> VoidResult {
   unsafe {
      ffi::glUniform1i(location, value)
   }

   gl_result(())
}

#[inline]
pub fn draw_arrays(count: ffi::GLsizei) -> VoidResult {
   unsafe {
      ffi::glDrawArrays(ffi::GL_TRIANGLE_STRIP, 0, count)
   }

   gl_result(())
}

pub struct Texture {
   pub name: ffi::GLuint,
}

impl Texture {
   pub fn new(width: u32, height: u32) -> Result<Self, RuntimeError> {
      let mut name: ffi::GLuint = unsafe { mem::uninitialized() };

      unsafe {
         ffi::glGenTextures(1, &mut name);

         ffi::glBindTexture(ffi::GL_TEXTURE_2D, name);

         ffi::glTexParameteri(
            ffi::GL_TEXTURE_2D,
            ffi::GL_TEXTURE_WRAP_S,
            ffi::GL_CLAMP_TO_EDGE as ffi::GLint
         );

         ffi::glTexParameteri(
            ffi::GL_TEXTURE_2D,
            ffi::GL_TEXTURE_WRAP_T,
            ffi::GL_CLAMP_TO_EDGE as ffi::GLint
         );

         ffi::glTexParameteri(
            ffi::GL_TEXTURE_2D,
            ffi::GL_TEXTURE_MIN_FILTER,
            ffi::GL_NEAREST as ffi::GLint
         );

         ffi::glTexParameteri(
            ffi::GL_TEXTURE_2D,
            ffi::GL_TEXTURE_MAG_FILTER,
            ffi::GL_NEAREST as ffi::GLint
         );

         ffi::glTexImage2D(
            ffi::GL_TEXTURE_2D,
            0,
            ffi::GL_RGBA8 as ffi::GLint,
            width as ffi::GLsizei,
            height as ffi::GLsizei,
            0,
            ffi::GL_RGBA,
            ffi::GL_UNSIGNED_BYTE,
            ptr::null()
         );
      }

      gl_result(Texture {
         name: name,
      })
   }

   #[inline]
   pub fn bind(&self) -> VoidResult {
      unsafe {
         ffi::glBindTexture(ffi::GL_TEXTURE_2D, self.name);
      }

      gl_result(())
   }

   #[inline]
   pub fn resize(&self, width: u32, height: u32) -> VoidResult {
      unsafe {
         ffi::glTexImage2D(
            ffi::GL_TEXTURE_2D,
            0,
            ffi::GL_RGBA8 as ffi::GLint,
            width as ffi::GLsizei,
            height as ffi::GLsizei,
            0,
            ffi::GL_RGBA,
            ffi::GL_UNSIGNED_BYTE,
            ptr::null()
         );
      }

      gl_result(())
   }

   #[inline]
   pub fn null_update(&self, width: u32, height: u32) -> VoidResult {
      unsafe {
         ffi::glTexSubImage2D(
            ffi::GL_TEXTURE_2D,
            0,
            0, 0, width as ffi::GLsizei, height as ffi::GLsizei,
            ffi::GL_RGBA,
            ffi::GL_UNSIGNED_BYTE,
            ptr::null()
         );
      }

      gl_result(())
   }

   #[inline]
   pub fn update(&self, width: u32, height: u32, data: &[u8]) -> VoidResult {
      unsafe {
         ffi::glTexSubImage2D(
            ffi::GL_TEXTURE_2D,
            0,
            0, 0, width as ffi::GLsizei, height as ffi::GLsizei,
            ffi::GL_RGBA,
            ffi::GL_UNSIGNED_BYTE,
            data.as_ptr() as *const ffi::GLvoid
         );
      }

      gl_result(())
   }
}

impl Drop for Texture {
   fn drop (&mut self) {
      unsafe {
         ffi::glDeleteTextures(1, &self.name)
      };
   }
}

pub struct Framebuffer {
   pub name: ffi::GLuint,
}

impl Framebuffer {
   pub fn new(texture: &Texture) -> Result<Self, RuntimeError> {
      let name = try!(Framebuffer::get_framebuffers());

      let framebuffer = Framebuffer {
         name: name,
      };

      try!(framebuffer.bind());
      try!(framebuffer.attach_texture(texture));
      try!(framebuffer.unbind());

      Ok(framebuffer)
   }

   #[inline]
   pub fn get_framebuffers() -> Result<ffi::GLuint, RuntimeError> {
      let mut name: ffi::GLuint = unsafe { mem::uninitialized() };

      unsafe {
         ffi::glGenFramebuffers(1, &mut name)
      };

      gl_result(name)
   }

   #[inline]
   pub fn bind(&self) -> VoidResult {
      unsafe {
         ffi::glBindFramebuffer(ffi::GL_READ_FRAMEBUFFER, self.name)
      };

      gl_result(())
   }

   #[inline]
   pub fn unbind(&self) -> VoidResult {
      unsafe {
         ffi::glBindFramebuffer(ffi::GL_READ_FRAMEBUFFER, 0)
      };

      gl_result(())
   }

   #[inline]
   pub fn attach_texture(&self, texture: &Texture) -> VoidResult {
      unsafe {
         ffi::glFramebufferTexture2D(
            ffi::GL_READ_FRAMEBUFFER,
            ffi::GL_COLOR_ATTACHMENT0,
            ffi::GL_TEXTURE_2D,
            texture.name,
            0
         )
      };

      gl_result(())
   }

   #[inline]
   pub fn blit(&self, width: u32, height: u32) -> VoidResult {
      unsafe {
         ffi::glBlitFramebuffer(
            0, 0, width as ffi::GLint, height as ffi::GLint,
            0, 0, width as ffi::GLint, height as ffi::GLint,
            ffi::GL_COLOR_BUFFER_BIT,
            ffi::GL_NEAREST
         )
      };

      gl_result(())
   }
}

impl Drop for Framebuffer {
   fn drop (&mut self) {
      unsafe {
         ffi::glDeleteFramebuffers(1, &self.name)
      };
   }
}

pub struct Buffer {
   pub name: ffi::GLuint,
   pub ptr: *mut ffi::c_void,
   pub size: ffi::GLsizeiptr,
}

impl Buffer {
   pub fn new() -> Result<Self, RuntimeError> {
      let mut name: ffi::GLuint = unsafe { mem::uninitialized() };

      unsafe {
         ffi::glGenBuffers(1, &mut name)
      };

      gl_result(Buffer {
         name: name,
         ptr: ptr::null_mut(),
         size: 0,
      })
   }

   #[inline]
   pub fn bind(&self) -> VoidResult {
      unsafe {
         ffi::glBindBuffer(ffi::GL_PIXEL_UNPACK_BUFFER, self.name)
      };

      gl_result(())
   }

   #[inline]
   pub fn unbind(&self) -> VoidResult {
      unsafe {
         ffi::glBindBuffer(ffi::GL_PIXEL_UNPACK_BUFFER, 0)
      };

      gl_result(())
   }

   #[inline]
   pub fn init_data(&mut self, size: usize) -> VoidResult {
      self.size = size as ffi::GLsizeiptr;
      unsafe {
         ffi::glBufferData(
            ffi::GL_PIXEL_UNPACK_BUFFER,
            self.size,
            ptr::null(),
            ffi::GL_STREAM_DRAW
         )
      };

      gl_result(())
   }

   #[inline]
   pub fn map(&mut self) -> VoidResult {
      self.ptr = unsafe {
         ffi::glMapBuffer(ffi::GL_PIXEL_UNPACK_BUFFER, ffi::GL_WRITE_ONLY)
      };

      gl_result(())
   }

   #[inline]
   pub fn map_range(&mut self) -> VoidResult {
      self.ptr = unsafe {
         ffi::glMapBufferRange(
            ffi::GL_PIXEL_UNPACK_BUFFER, 0, self.size,
            ffi::GL_MAP_WRITE_BIT// | ffi::GL_MAP_UNSYNCHRONIZED_BIT
         )
      };

      gl_result(())
   }

   #[inline]
   pub fn unmap(&self) -> VoidResult {
      unsafe {
         ffi::glUnmapBuffer(ffi::GL_PIXEL_UNPACK_BUFFER)
      };

      gl_result(())
   }
}

impl Drop for Buffer {
   fn drop (&mut self) {
      unsafe {
         ffi::glDeleteBuffers(1, &self.name)
      };
   }
}

pub struct Shader {
   pub name: ffi::GLuint,
}

impl Shader {
   pub fn new(shader_type: ffi::GLenum) -> Result<Self, RuntimeError> {
      let name = unsafe {
         ffi::glCreateShader(shader_type)
      };

      gl_result(Shader {
         name: name,
      })
   }

   #[inline]
   pub fn shader_source(&self, string: &str) -> VoidResult {
      let cstring = CString::new(string).unwrap().as_ptr();

      unsafe {
         ffi::glShaderSource(self.name, 1, &cstring, ptr::null())
      };

      gl_result(())
   }

   #[inline]
   pub fn compile(&self) -> VoidResult {
      unsafe {
         ffi::glCompileShader(self.name)
      };

      gl_result(())
   }

   #[inline]
   pub fn is_compiled(&self) -> Result<bool, RuntimeError> {
      let mut compiled: ffi::GLint = unsafe { mem::uninitialized() };

      unsafe {
         ffi::glGetShaderiv(self.name, ffi::GL_COMPILE_STATUS, &mut compiled);
      };

      gl_result(compiled == ffi::GL_TRUE as ffi::GLint)
   }
}

impl Drop for Shader {
   fn drop (&mut self) {
      unsafe {
         ffi::glDeleteShader(self.name)
      };
   }
}

pub struct Program {
   pub name: ffi::GLuint,
}

impl Program {
   pub fn new() -> Result<Self, RuntimeError> {
      let name = unsafe {
         ffi::glCreateProgram()
      };

      gl_result(Program {
         name: name,
      })
   }

   #[inline]
   pub fn attach_shader(&self, shader: &Shader) -> VoidResult {
      unsafe {
         ffi::glAttachShader(self.name, shader.name)
      };

      gl_result(())
   }

   #[inline]
   pub fn link(&self) -> VoidResult {
      unsafe {
         ffi::glLinkProgram(self.name)
      };

      gl_result(())
   }

   #[inline]
   pub fn use_program(&self) -> VoidResult {
      unsafe {
         ffi::glUseProgram(self.name)
      };

      gl_result(())
   }

   #[inline]
   pub fn is_linked(&self) -> Result<bool, RuntimeError> {
      let mut linked: ffi::GLint = unsafe { mem::uninitialized() };

      unsafe {
         ffi::glGetProgramiv(self.name, ffi::GL_LINK_STATUS, &mut linked);
      };

      gl_result(linked == ffi::GL_TRUE as ffi::GLint)
   }

   #[inline]
   pub fn get_attrib_location(&self, attrib_name: &str) -> Result<ffi::GLint, RuntimeError> {
      let cname = CString::new(attrib_name).unwrap().as_ptr();

      let result = unsafe {
         ffi::glGetAttribLocation(self.name, cname)
      };

      gl_result(result)
   }

   #[inline]
   pub fn get_uniform_location(&self, variable_name: &str) -> Result<ffi::GLint, RuntimeError> {
      let cname = CString::new(variable_name).unwrap().as_ptr();

      let result = unsafe {
         ffi::glGetUniformLocation(self.name, cname)
      };

      gl_result(result)
   }
}

impl Drop for Program {
   fn drop (&mut self) {
      unsafe {
         ffi::glDeleteProgram(self.name)
      };
   }
}
<commit_msg>Imroved error detection for GL shader compilation and program linking<commit_after>pub mod ffi;
pub mod frame;

use std::mem;
use std::ptr;
use std::ffi::CString;

use error::{RuntimeError, ErrorKind, VoidResult};

use super::utils::fn_ptr::FnPtrLoader;

#[inline]
pub fn load<T: FnPtrLoader>(loader: &T) {
   unsafe {
      ffi::load_functions(loader)
   };
}

#[inline]
fn gl_error<T>(message: &str) -> Result<T, RuntimeError> {
   Err(
      RuntimeError::new(
         ErrorKind::GL,
         message.to_string()
      )
   )
}

#[inline]
fn gl_result<T>(value: T) -> Result<T, RuntimeError> {
   let result = unsafe {
      ffi::glGetError()
   };

   match result {
      ffi::GL_NO_ERROR => Ok(value),
      ffi::GL_INVALID_ENUM => gl_error(
         "An unacceptable value is specified for an enumerated argument"
      ),
      ffi::GL_INVALID_VALUE => gl_error(
         "A numeric argument is out of range"
      ),
      ffi::GL_INVALID_OPERATION => gl_error(
         "The specified operation is not allowed in the current state"
      ),
      ffi::GL_INVALID_FRAMEBUFFER_OPERATION => gl_error(
         "The framebuffer object is not complete"
      ),
      ffi::GL_OUT_OF_MEMORY => gl_error(
         "There is not enough memory left to execute the command"
      ),
      ffi::GL_STACK_UNDERFLOW => gl_error(
         "Performing an operation that would cause an internal stack to underflow"
      ),
      ffi::GL_STACK_OVERFLOW => gl_error(
         "Performing an operation that would cause an internal stack to overflow"
      ),
      _ => gl_error("Unknown OpenGL error")
   }
}

#[inline]
pub fn reset_pixelstore_alignment() -> VoidResult {
   unsafe {
      ffi::glPixelStorei(ffi::GL_UNPACK_ALIGNMENT, 1);
   }

   gl_result(())
}

#[inline]
pub fn enable_framebuffer_srgb() -> VoidResult {
   unsafe {
      ffi::glEnable(ffi::GL_FRAMEBUFFER_SRGB);
   }

   gl_result(())
}

#[inline]
pub fn viewport(width: u32, height: u32) -> VoidResult {
   unsafe {
      ffi::glViewport(0, 0, width as ffi::GLsizei, height as ffi::GLsizei)
   }

   gl_result(())
}

#[inline]
pub fn vertex_attrib_pointer(
   index: ffi::GLuint, size: ffi::GLint, buffer: &[ffi::GLfloat]
) -> VoidResult {

   unsafe {
      ffi::glVertexAttribPointer(
         index,
         size,
         ffi::GL_FLOAT,
         ffi::GL_FALSE,
         0,
         buffer.as_ptr() as *const ffi::GLvoid
      )
   }

   gl_result(())
}

#[inline]
pub fn enable_vertex_attrib_array(index: ffi::GLuint) -> VoidResult {
   unsafe {
      ffi::glEnableVertexAttribArray(index)
   }

   gl_result(())
}

#[inline]
pub fn uniform_value_1i(location: ffi::GLint, value: ffi::GLint) -> VoidResult {
   unsafe {
      ffi::glUniform1i(location, value)
   }

   gl_result(())
}

#[inline]
pub fn draw_arrays(count: ffi::GLsizei) -> VoidResult {
   unsafe {
      ffi::glDrawArrays(ffi::GL_TRIANGLE_STRIP, 0, count)
   }

   gl_result(())
}

pub struct Texture {
   pub name: ffi::GLuint,
}

impl Texture {
   pub fn new(width: u32, height: u32) -> Result<Self, RuntimeError> {
      let mut name: ffi::GLuint = unsafe { mem::uninitialized() };

      unsafe {
         ffi::glGenTextures(1, &mut name);

         ffi::glBindTexture(ffi::GL_TEXTURE_2D, name);

         ffi::glTexParameteri(
            ffi::GL_TEXTURE_2D,
            ffi::GL_TEXTURE_WRAP_S,
            ffi::GL_CLAMP_TO_EDGE as ffi::GLint
         );

         ffi::glTexParameteri(
            ffi::GL_TEXTURE_2D,
            ffi::GL_TEXTURE_WRAP_T,
            ffi::GL_CLAMP_TO_EDGE as ffi::GLint
         );

         ffi::glTexParameteri(
            ffi::GL_TEXTURE_2D,
            ffi::GL_TEXTURE_MIN_FILTER,
            ffi::GL_NEAREST as ffi::GLint
         );

         ffi::glTexParameteri(
            ffi::GL_TEXTURE_2D,
            ffi::GL_TEXTURE_MAG_FILTER,
            ffi::GL_NEAREST as ffi::GLint
         );

         ffi::glTexImage2D(
            ffi::GL_TEXTURE_2D,
            0,
            ffi::GL_RGBA8 as ffi::GLint,
            width as ffi::GLsizei,
            height as ffi::GLsizei,
            0,
            ffi::GL_RGBA,
            ffi::GL_UNSIGNED_BYTE,
            ptr::null()
         );
      }

      gl_result(Texture {
         name: name,
      })
   }

   #[inline]
   pub fn bind(&self) -> VoidResult {
      unsafe {
         ffi::glBindTexture(ffi::GL_TEXTURE_2D, self.name);
      }

      gl_result(())
   }

   #[inline]
   pub fn resize(&self, width: u32, height: u32) -> VoidResult {
      unsafe {
         ffi::glTexImage2D(
            ffi::GL_TEXTURE_2D,
            0,
            ffi::GL_RGBA8 as ffi::GLint,
            width as ffi::GLsizei,
            height as ffi::GLsizei,
            0,
            ffi::GL_RGBA,
            ffi::GL_UNSIGNED_BYTE,
            ptr::null()
         );
      }

      gl_result(())
   }

   #[inline]
   pub fn null_update(&self, width: u32, height: u32) -> VoidResult {
      unsafe {
         ffi::glTexSubImage2D(
            ffi::GL_TEXTURE_2D,
            0,
            0, 0, width as ffi::GLsizei, height as ffi::GLsizei,
            ffi::GL_RGBA,
            ffi::GL_UNSIGNED_BYTE,
            ptr::null()
         );
      }

      gl_result(())
   }

   #[inline]
   pub fn update(&self, width: u32, height: u32, data: &[u8]) -> VoidResult {
      unsafe {
         ffi::glTexSubImage2D(
            ffi::GL_TEXTURE_2D,
            0,
            0, 0, width as ffi::GLsizei, height as ffi::GLsizei,
            ffi::GL_RGBA,
            ffi::GL_UNSIGNED_BYTE,
            data.as_ptr() as *const ffi::GLvoid
         );
      }

      gl_result(())
   }
}

impl Drop for Texture {
   fn drop (&mut self) {
      unsafe {
         ffi::glDeleteTextures(1, &self.name)
      };
   }
}

pub struct Framebuffer {
   pub name: ffi::GLuint,
}

impl Framebuffer {
   pub fn new(texture: &Texture) -> Result<Self, RuntimeError> {
      let name = try!(Framebuffer::get_framebuffers());

      let framebuffer = Framebuffer {
         name: name,
      };

      try!(framebuffer.bind());
      try!(framebuffer.attach_texture(texture));
      try!(framebuffer.unbind());

      Ok(framebuffer)
   }

   #[inline]
   pub fn get_framebuffers() -> Result<ffi::GLuint, RuntimeError> {
      let mut name: ffi::GLuint = unsafe { mem::uninitialized() };

      unsafe {
         ffi::glGenFramebuffers(1, &mut name)
      };

      gl_result(name)
   }

   #[inline]
   pub fn bind(&self) -> VoidResult {
      unsafe {
         ffi::glBindFramebuffer(ffi::GL_READ_FRAMEBUFFER, self.name)
      };

      gl_result(())
   }

   #[inline]
   pub fn unbind(&self) -> VoidResult {
      unsafe {
         ffi::glBindFramebuffer(ffi::GL_READ_FRAMEBUFFER, 0)
      };

      gl_result(())
   }

   #[inline]
   pub fn attach_texture(&self, texture: &Texture) -> VoidResult {
      unsafe {
         ffi::glFramebufferTexture2D(
            ffi::GL_READ_FRAMEBUFFER,
            ffi::GL_COLOR_ATTACHMENT0,
            ffi::GL_TEXTURE_2D,
            texture.name,
            0
         )
      };

      gl_result(())
   }

   #[inline]
   pub fn blit(&self, width: u32, height: u32) -> VoidResult {
      unsafe {
         ffi::glBlitFramebuffer(
            0, 0, width as ffi::GLint, height as ffi::GLint,
            0, 0, width as ffi::GLint, height as ffi::GLint,
            ffi::GL_COLOR_BUFFER_BIT,
            ffi::GL_NEAREST
         )
      };

      gl_result(())
   }
}

impl Drop for Framebuffer {
   fn drop (&mut self) {
      unsafe {
         ffi::glDeleteFramebuffers(1, &self.name)
      };
   }
}

pub struct Buffer {
   pub name: ffi::GLuint,
   pub ptr: *mut ffi::c_void,
   pub size: ffi::GLsizeiptr,
}

impl Buffer {
   pub fn new() -> Result<Self, RuntimeError> {
      let mut name: ffi::GLuint = unsafe { mem::uninitialized() };

      unsafe {
         ffi::glGenBuffers(1, &mut name)
      };

      gl_result(Buffer {
         name: name,
         ptr: ptr::null_mut(),
         size: 0,
      })
   }

   #[inline]
   pub fn bind(&self) -> VoidResult {
      unsafe {
         ffi::glBindBuffer(ffi::GL_PIXEL_UNPACK_BUFFER, self.name)
      };

      gl_result(())
   }

   #[inline]
   pub fn unbind(&self) -> VoidResult {
      unsafe {
         ffi::glBindBuffer(ffi::GL_PIXEL_UNPACK_BUFFER, 0)
      };

      gl_result(())
   }

   #[inline]
   pub fn init_data(&mut self, size: usize) -> VoidResult {
      self.size = size as ffi::GLsizeiptr;
      unsafe {
         ffi::glBufferData(
            ffi::GL_PIXEL_UNPACK_BUFFER,
            self.size,
            ptr::null(),
            ffi::GL_STREAM_DRAW
         )
      };

      gl_result(())
   }

   #[inline]
   pub fn map(&mut self) -> VoidResult {
      self.ptr = unsafe {
         ffi::glMapBuffer(ffi::GL_PIXEL_UNPACK_BUFFER, ffi::GL_WRITE_ONLY)
      };

      gl_result(())
   }

   #[inline]
   pub fn map_range(&mut self) -> VoidResult {
      self.ptr = unsafe {
         ffi::glMapBufferRange(
            ffi::GL_PIXEL_UNPACK_BUFFER, 0, self.size,
            ffi::GL_MAP_WRITE_BIT// | ffi::GL_MAP_UNSYNCHRONIZED_BIT
         )
      };

      gl_result(())
   }

   #[inline]
   pub fn unmap(&self) -> VoidResult {
      unsafe {
         ffi::glUnmapBuffer(ffi::GL_PIXEL_UNPACK_BUFFER)
      };

      gl_result(())
   }
}

impl Drop for Buffer {
   fn drop (&mut self) {
      unsafe {
         ffi::glDeleteBuffers(1, &self.name)
      };
   }
}

pub struct Shader {
   pub name: ffi::GLuint,
}

impl Shader {
   pub fn new(shader_type: ffi::GLenum) -> Result<Self, RuntimeError> {
      let name = unsafe {
         ffi::glCreateShader(shader_type)
      };

      gl_result(Shader {
         name: name,
      })
   }

   #[inline]
   pub fn shader_source(&self, string: &str) -> VoidResult {
      let cstring = CString::new(string).unwrap().as_ptr();

      unsafe {
         ffi::glShaderSource(self.name, 1, &cstring, ptr::null())
      };

      gl_result(())
   }

   #[inline]
   pub fn compile(&self) -> VoidResult {
      unsafe {
         ffi::glCompileShader(self.name)
      };

      if !try!(self.is_compiled()) {
         return gl_error("Shader not compiled")
      }

      Ok(())
   }

   #[inline]
   pub fn is_compiled(&self) -> Result<bool, RuntimeError> {
      let mut compiled: ffi::GLint = unsafe { mem::uninitialized() };

      unsafe {
         ffi::glGetShaderiv(self.name, ffi::GL_COMPILE_STATUS, &mut compiled);
      };

      gl_result(compiled == ffi::GL_TRUE as ffi::GLint)
   }
}

impl Drop for Shader {
   fn drop (&mut self) {
      unsafe {
         ffi::glDeleteShader(self.name)
      };
   }
}

pub struct Program {
   pub name: ffi::GLuint,
}

impl Program {
   pub fn new() -> Result<Self, RuntimeError> {
      let name = unsafe {
         ffi::glCreateProgram()
      };

      gl_result(Program {
         name: name,
      })
   }

   #[inline]
   pub fn attach_shader(&self, shader: &Shader) -> VoidResult {
      unsafe {
         ffi::glAttachShader(self.name, shader.name)
      };

      gl_result(())
   }

   #[inline]
   pub fn link(&self) -> VoidResult {
      unsafe {
         ffi::glLinkProgram(self.name)
      };

      if !try!(self.is_linked()) {
         return gl_error("Program not linked")
      }

      Ok(())
   }

   #[inline]
   pub fn use_program(&self) -> VoidResult {
      unsafe {
         ffi::glUseProgram(self.name)
      };

      gl_result(())
   }

   #[inline]
   pub fn is_linked(&self) -> Result<bool, RuntimeError> {
      let mut linked: ffi::GLint = unsafe { mem::uninitialized() };

      unsafe {
         ffi::glGetProgramiv(self.name, ffi::GL_LINK_STATUS, &mut linked);
      };

      gl_result(linked == ffi::GL_TRUE as ffi::GLint)
   }

   #[inline]
   pub fn get_attrib_location(&self, attrib_name: &str) -> Result<ffi::GLint, RuntimeError> {
      let cname = CString::new(attrib_name).unwrap().as_ptr();

      let result = unsafe {
         ffi::glGetAttribLocation(self.name, cname)
      };

      gl_result(result)
   }

   #[inline]
   pub fn get_uniform_location(&self, variable_name: &str) -> Result<ffi::GLint, RuntimeError> {
      let cname = CString::new(variable_name).unwrap().as_ptr();

      let result = unsafe {
         ffi::glGetUniformLocation(self.name, cname)
      };

      gl_result(result)
   }
}

impl Drop for Program {
   fn drop (&mut self) {
      unsafe {
         ffi::glDeleteProgram(self.name)
      };
   }
}
<|endoftext|>


<commit_before>extern crate clap;
extern crate termion;

use std::collections::HashMap;
use std::cmp::{max, min};
use std::fs::File;
use std::io::{stdin, stdout, BufRead, BufReader, Write};
use std::option::Option;
use std::path::Path;
use std::process::{Command, Stdio};
use std::sync::mpsc::{channel, Receiver, Sender};
use std::vec::Vec;

use std::thread;

use clap::{App, Arg};
use termion::color;
use termion::event::{Event, Key, MouseEvent};
use termion::input::{MouseTerminal, TermRead};
use termion::raw::IntoRawMode;

const VERT_SEPARATOR: &'static str = "│";
const LIST_WIDTH: u16 = 32;

// FIXME: Support handling SIGWINCH for rerender on resize.
enum ChanMessage {
    SearchResult(ListEntry),
    SearchComplete,
    TerminalEvent(Event),
}

#[derive(Debug, Clone)]
struct ListEntry {
    directory: String,
    file_name: String,
    line_number: u32,
    text: String,
}

impl ListEntry {
    // format is: path/to/file.rs:123:line of text
    fn from_line(line: &str) -> Option<ListEntry> {
        let parts: Vec<&str> = line.splitn(3, ':').collect();

        if parts.len() != 3 {
            return None;
        }

        let path = Path::new(parts[0]);

        // FIXME: This cannot possibly be right. wtf.
        Some(ListEntry {
            directory: String::from(path.parent().unwrap().to_str().unwrap()),
            file_name: String::from(path.file_name().unwrap().to_str().unwrap()),
            line_number: parts[1].parse().unwrap(),
            text: String::from(parts[2]),
        })
    }
}

#[derive(Debug, Clone)]
struct FileList {
    entries: Vec<ListEntry>,
    current_index: Option<usize>,
}

impl FileList {
    fn new() -> FileList {
        FileList {
            entries: vec![],
            current_index: None,
        }
    }

    fn add_entry(&mut self, entry: ListEntry) {
        // Insert the new element into it's sorted position
        let insert_pos = {
            let new_key = (&entry.directory, &entry.file_name, &entry.line_number);
            let result = self.entries.binary_search_by(|other| {
                let k = (&other.directory, &other.file_name, &other.line_number);
                return k.cmp(&new_key);
            });

            match result {
                Ok(pos) | Err(pos) => pos,
            }
        };

        self.entries.insert(insert_pos, entry);

        if self.current_index.is_none() {
            self.current_index = Some(0);
        }
    }

    fn scroll_down(&mut self) {
        self.current_index = self.current_index
            .map(|prev| min(self.entries.len() - 1, prev + 1))
    }

    fn scroll_up(&mut self) {
        self.current_index = self.current_index
            .map(|prev| if prev == 0 { prev } else { prev - 1 })
    }
}

#[derive(Debug)]
struct UI {
    list: FileList,
    file_cache: HashMap<(String, String), File>,
}

fn main() {
    let matches = App::new("dredge")
        .about("Search interactively")
        .arg(
            Arg::with_name("program")
                .long("program")
                .short("p")
                .value_name("PROGRAM")
                .default_value("ag")
                .help("grep or ag or w/e"),
        )
        .arg(
            Arg::with_name("query")
                .help("what to search for")
                .required(true),
        )
        // .arg(
        //     Arg::with_name("directories")
        //         .help("where to search for it")
        //         .multiple(true)
        //         .default_value("."),
        // )
        // .arg(
        //     Arg::with_name("options")
        //         .help("options to pass directly to search program.")
        //         .multiple(true)
        //         .default_value("")
        //         .last(true),
        // )
        .get_matches();

    let (tx, rx) = channel();

    let cmd_tx = tx.clone();
    let term_tx = tx.clone();

    thread::spawn(move || {
        run_command(matches, cmd_tx).expect("failed to run command");
    });

    thread::spawn(move || {
        let stdin = stdin();

        for c in stdin.events() {
            term_tx
                .send(ChanMessage::TerminalEvent(c.unwrap()))
                .unwrap();
        }
    });

    ui_loop(rx).unwrap();
}

fn run_command(
    config: clap::ArgMatches,
    sender: Sender<ChanMessage>,
) -> Result<(), Box<std::error::Error>> {
    let cmd = Command::new("ag")
        .arg(config.value_of("query").unwrap())
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .spawn()?;

    let output = cmd.stdout.unwrap();
    let reader = BufReader::new(output);

    for line in reader.lines() {
        if let Some(entry) = ListEntry::from_line(&line.unwrap()) {
            sender.send(ChanMessage::SearchResult(entry))?;
        }
    }

    sender.send(ChanMessage::SearchComplete)?;

    Ok(())
}

fn ui_loop(receiver: Receiver<ChanMessage>) -> Result<(), Box<std::error::Error>> {
    let mut file_list = FileList::new();

    let mut stdout = MouseTerminal::from(stdout().into_raw_mode()?);
    write!(
        stdout,
        "{}{}",
        termion::clear::All,
        termion::cursor::Goto(1, 1)
    )?;

    let mut min_index = 0;

    for entry in receiver.iter() {
        let (_width, height) = termion::terminal_size()?;
        let mut redraw = true;

        match entry {
            ChanMessage::SearchComplete => {
                if file_list.entries.is_empty() {
                    println!("No matches");
                    break;
                }
            }

            ChanMessage::TerminalEvent(evt) => match evt {
                Event::Key(Key::Ctrl('c')) | Event::Key(Key::Char('q')) => break,

                Event::Key(Key::Char('j')) => {
                    file_list.scroll_down();
                }

                Event::Key(Key::Char('k')) => {
                    file_list.scroll_up();
                }

                Event::Mouse(me) => match me {
                    MouseEvent::Press(_, x, y) => {
                        write!(stdout, "{}x", termion::cursor::Goto(x, y))?;
                        stdout.flush()?;
                    }

                    _ => (),
                },

                _ => {
                    redraw = false;
                }
            },

            ChanMessage::SearchResult(entry) => {
                redraw = true;
                file_list.add_entry(entry);

                if file_list.current_index.is_none() {
                    file_list.current_index = Some(0)
                }
            }
        }

        if redraw {
            min_index = file_list.current_index.map_or(0, |cur| {
                if cur < min_index {
                    min_index - 1
                } else if cur >= min_index + height as usize {
                    cur
                } else {
                    min_index
                }
            });

            for row in 0..height {
                write!(stdout, "{}", termion::cursor::Goto(1, 1 + row as u16))?;

                let index = (min_index + row as usize) as usize;

                if index < file_list.entries.len() {
                    let ref entry = &file_list.entries[index];
                    let (fg, bg): (&color::Color, &color::Color) =
                        if Some(index) == file_list.current_index {
                            (&color::Black, &color::White)
                        } else {
                            (&color::Reset, &color::Reset)
                        };

                    let entry_fmt = format!(
                        "{}/{}:{}",
                        entry.directory, entry.file_name, entry.line_number
                    );

                    write!(
                        stdout,
                        "{}{}{:<32}{}{}",
                        color::Fg(fg),
                        color::Bg(bg),
                        entry_fmt,
                        color::Fg(color::Reset),
                        color::Bg(color::Reset),
                    )?;
                } else {
                    write!(stdout, "{:<32}", "")?;
                }

                write!(
                    stdout,
                    "{}{}",
                    termion::cursor::Goto(LIST_WIDTH, 1 + row),
                    VERT_SEPARATOR
                )?;
            }

            write!(stdout, "{}", termion::cursor::Hide)?;
            stdout.flush()?;
        }
    }

    Ok(())
}
<commit_msg>Implement mouse selection<commit_after>extern crate clap;
extern crate termion;

use std::collections::HashMap;
use std::cmp::{max, min};
use std::fs::File;
use std::io::{stdin, stdout, BufRead, BufReader, Write};
use std::option::Option;
use std::path::Path;
use std::process::{Command, Stdio};
use std::sync::mpsc::{channel, Receiver, Sender};
use std::vec::Vec;

use std::thread;

use clap::{App, Arg};
use termion::color;
use termion::event::{Event, Key, MouseEvent};
use termion::input::{MouseTerminal, TermRead};
use termion::raw::IntoRawMode;

const VERT_SEPARATOR: &'static str = "│";
const LIST_WIDTH: u16 = 32;

// FIXME: Support handling SIGWINCH for rerender on resize.
enum ChanMessage {
    SearchResult(ListEntry),
    SearchComplete,
    TerminalEvent(Event),
}

#[derive(Debug, Clone)]
struct ListEntry {
    directory: String,
    file_name: String,
    line_number: u32,
    text: String,
}

impl ListEntry {
    // format is: path/to/file.rs:123:line of text
    fn from_line(line: &str) -> Option<ListEntry> {
        let parts: Vec<&str> = line.splitn(3, ':').collect();

        if parts.len() != 3 {
            return None;
        }

        let path = Path::new(parts[0]);

        // FIXME: This cannot possibly be right. wtf.
        Some(ListEntry {
            directory: String::from(path.parent().unwrap().to_str().unwrap()),
            file_name: String::from(path.file_name().unwrap().to_str().unwrap()),
            line_number: parts[1].parse().unwrap(),
            text: String::from(parts[2]),
        })
    }
}

#[derive(Debug, Clone)]
struct FileList {
    entries: Vec<ListEntry>,
    current_index: Option<usize>,
}

impl FileList {
    fn new() -> FileList {
        FileList {
            entries: vec![],
            current_index: None,
        }
    }

    fn add_entry(&mut self, entry: ListEntry) {
        // Insert the new element into it's sorted position
        let insert_pos = {
            let new_key = (&entry.directory, &entry.file_name, &entry.line_number);
            let result = self.entries.binary_search_by(|other| {
                let k = (&other.directory, &other.file_name, &other.line_number);
                return k.cmp(&new_key);
            });

            match result {
                Ok(pos) | Err(pos) => pos,
            }
        };

        self.entries.insert(insert_pos, entry);

        if self.current_index.is_none() {
            self.current_index = Some(0);
        }
    }

    fn scroll_down(&mut self) {
        self.current_index = self.current_index
            .map(|prev| min(self.entries.len() - 1, prev + 1))
    }

    fn scroll_up(&mut self) {
        self.current_index = self.current_index
            .map(|prev| if prev == 0 { prev } else { prev - 1 })
    }
}

#[derive(Debug)]
struct UI {
    list: FileList,
    file_cache: HashMap<(String, String), File>,
}

fn main() {
    let matches = App::new("dredge")
        .about("Search interactively")
        .arg(
            Arg::with_name("program")
                .long("program")
                .short("p")
                .value_name("PROGRAM")
                .default_value("ag")
                .help("grep or ag or w/e"),
        )
        .arg(
            Arg::with_name("query")
                .help("what to search for")
                .required(true),
        )
        // .arg(
        //     Arg::with_name("directories")
        //         .help("where to search for it")
        //         .multiple(true)
        //         .default_value("."),
        // )
        // .arg(
        //     Arg::with_name("options")
        //         .help("options to pass directly to search program.")
        //         .multiple(true)
        //         .default_value("")
        //         .last(true),
        // )
        .get_matches();

    let (tx, rx) = channel();

    let cmd_tx = tx.clone();
    let term_tx = tx.clone();

    thread::spawn(move || {
        run_command(matches, cmd_tx).expect("failed to run command");
    });

    thread::spawn(move || {
        let stdin = stdin();

        for c in stdin.events() {
            term_tx
                .send(ChanMessage::TerminalEvent(c.unwrap()))
                .unwrap();
        }
    });

    ui_loop(rx).unwrap();
}

fn run_command(
    config: clap::ArgMatches,
    sender: Sender<ChanMessage>,
) -> Result<(), Box<std::error::Error>> {
    let cmd = Command::new("ag")
        .arg(config.value_of("query").unwrap())
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .spawn()?;

    let output = cmd.stdout.unwrap();
    let reader = BufReader::new(output);

    for line in reader.lines() {
        if let Some(entry) = ListEntry::from_line(&line.unwrap()) {
            sender.send(ChanMessage::SearchResult(entry))?;
        }
    }

    sender.send(ChanMessage::SearchComplete)?;

    Ok(())
}

fn ui_loop(receiver: Receiver<ChanMessage>) -> Result<(), Box<std::error::Error>> {
    let mut file_list = FileList::new();

    let mut stdout = MouseTerminal::from(stdout().into_raw_mode()?);
    write!(
        stdout,
        "{}{}",
        termion::clear::All,
        termion::cursor::Goto(1, 1)
    )?;

    let mut min_index = 0;

    for entry in receiver.iter() {
        let (_width, height) = termion::terminal_size()?;
        let mut redraw = true;

        match entry {
            ChanMessage::SearchComplete => {
                if file_list.entries.is_empty() {
                    println!("No matches");
                    break;
                }
            }

            ChanMessage::TerminalEvent(evt) => match evt {
                Event::Key(Key::Ctrl('c')) | Event::Key(Key::Char('q')) => break,

                Event::Key(Key::Char('j')) => {
                    file_list.scroll_down();
                }

                Event::Key(Key::Char('k')) => {
                    file_list.scroll_up();
                }

                Event::Mouse(me) => match me {
                    MouseEvent::Press(_, x, y) => {
                        if x < LIST_WIDTH {
                            // TODO: have standards.
                            file_list.current_index = file_list.current_index.map(|_| {
                                max(1, min(file_list.entries.len(), min_index + y as usize)) - 1
                            });
                        }
                    }

                    _ => (),
                },

                _ => {
                    redraw = false;
                }
            },

            ChanMessage::SearchResult(entry) => {
                redraw = true;
                file_list.add_entry(entry);

                if file_list.current_index.is_none() {
                    file_list.current_index = Some(0)
                }
            }
        }

        if redraw {
            min_index = file_list.current_index.map_or(0, |cur| {
                if cur < min_index {
                    min_index - 1
                } else if cur >= min_index + height as usize {
                    cur
                } else {
                    min_index
                }
            });

            for row in 0..height {
                write!(stdout, "{}", termion::cursor::Goto(1, 1 + row as u16))?;

                let index = (min_index + row as usize) as usize;

                if index < file_list.entries.len() {
                    let ref entry = &file_list.entries[index];
                    let (fg, bg): (&color::Color, &color::Color) =
                        if Some(index) == file_list.current_index {
                            (&color::Black, &color::White)
                        } else {
                            (&color::Reset, &color::Reset)
                        };

                    let entry_fmt = format!(
                        "{}/{}:{}",
                        entry.directory, entry.file_name, entry.line_number
                    );

                    write!(
                        stdout,
                        "{}{}{:<32}{}{}",
                        color::Fg(fg),
                        color::Bg(bg),
                        entry_fmt,
                        color::Fg(color::Reset),
                        color::Bg(color::Reset),
                    )?;
                } else {
                    write!(stdout, "{:<32}", "")?;
                }

                write!(
                    stdout,
                    "{}{}",
                    termion::cursor::Goto(LIST_WIDTH, 1 + row),
                    VERT_SEPARATOR
                )?;
            }

            write!(stdout, "{}", termion::cursor::Hide)?;
            stdout.flush()?;
        }
    }

    Ok(())
}
<|endoftext|>


<commit_before>use crate::io::driver::{Handle, Interest, Registration};

use mio::event::Source;
use std::fmt;
use std::io;
use std::ops::Deref;
use std::panic::{RefUnwindSafe, UnwindSafe};

cfg_io_driver! {
    /// Associates an I/O resource that implements the [`std::io::Read`] and/or
    /// [`std::io::Write`] traits with the reactor that drives it.
    ///
    /// `PollEvented` uses [`Registration`] internally to take a type that
    /// implements [`mio::event::Source`] as well as [`std::io::Read`] and or
    /// [`std::io::Write`] and associate it with a reactor that will drive it.
    ///
    /// Once the [`mio::event::Source`] type is wrapped by `PollEvented`, it can be
    /// used from within the future's execution model. As such, the
    /// `PollEvented` type provides [`AsyncRead`] and [`AsyncWrite`]
    /// implementations using the underlying I/O resource as well as readiness
    /// events provided by the reactor.
    ///
    /// **Note**: While `PollEvented` is `Sync` (if the underlying I/O type is
    /// `Sync`), the caller must ensure that there are at most two tasks that
    /// use a `PollEvented` instance concurrently. One for reading and one for
    /// writing. While violating this requirement is "safe" from a Rust memory
    /// model point of view, it will result in unexpected behavior in the form
    /// of lost notifications and tasks hanging.
    ///
    /// ## Readiness events
    ///
    /// Besides just providing [`AsyncRead`] and [`AsyncWrite`] implementations,
    /// this type also supports access to the underlying readiness event stream.
    /// While similar in function to what [`Registration`] provides, the
    /// semantics are a bit different.
    ///
    /// Two functions are provided to access the readiness events:
    /// [`poll_read_ready`] and [`poll_write_ready`]. These functions return the
    /// current readiness state of the `PollEvented` instance. If
    /// [`poll_read_ready`] indicates read readiness, immediately calling
    /// [`poll_read_ready`] again will also indicate read readiness.
    ///
    /// When the operation is attempted and is unable to succeed due to the I/O
    /// resource not being ready, the caller must call `clear_readiness`.
    /// This clears the readiness state until a new readiness event is received.
    ///
    /// This allows the caller to implement additional functions. For example,
    /// [`TcpListener`] implements poll_accept by using [`poll_read_ready`] and
    /// `clear_read_ready`.
    ///
    /// ## Platform-specific events
    ///
    /// `PollEvented` also allows receiving platform-specific `mio::Ready` events.
    /// These events are included as part of the read readiness event stream. The
    /// write readiness event stream is only for `Ready::writable()` events.
    ///
    /// [`AsyncRead`]: crate::io::AsyncRead
    /// [`AsyncWrite`]: crate::io::AsyncWrite
    /// [`TcpListener`]: crate::net::TcpListener
    /// [`poll_read_ready`]: Registration::poll_read_ready
    /// [`poll_write_ready`]: Registration::poll_write_ready
    pub(crate) struct PollEvented<E: Source> {
        io: Option<E>,
        registration: Registration,
    }
}

// ===== impl PollEvented =====

impl<E: Source> PollEvented<E> {
    /// Creates a new `PollEvented` associated with the default reactor.
    ///
    /// # Panics
    ///
    /// This function panics if thread-local runtime is not set.
    ///
    /// The runtime is usually set implicitly when this function is called
    /// from a future driven by a tokio runtime, otherwise runtime can be set
    /// explicitly with [`Runtime::enter`](crate::runtime::Runtime::enter) function.
    #[track_caller]
    #[cfg_attr(feature = "signal", allow(unused))]
    pub(crate) fn new(io: E) -> io::Result<Self> {
        PollEvented::new_with_interest(io, Interest::READABLE | Interest::WRITABLE)
    }

    /// Creates a new `PollEvented` associated with the default reactor, for
    /// specific `Interest` state. `new_with_interest` should be used over `new`
    /// when you need control over the readiness state, such as when a file
    /// descriptor only allows reads. This does not add `hup` or `error` so if
    /// you are interested in those states, you will need to add them to the
    /// readiness state passed to this function.
    ///
    /// # Panics
    ///
    /// This function panics if thread-local runtime is not set.
    ///
    /// The runtime is usually set implicitly when this function is called from
    /// a future driven by a tokio runtime, otherwise runtime can be set
    /// explicitly with [`Runtime::enter`](crate::runtime::Runtime::enter)
    /// function.
    #[track_caller]
    #[cfg_attr(feature = "signal", allow(unused))]
    pub(crate) fn new_with_interest(io: E, interest: Interest) -> io::Result<Self> {
        Self::new_with_interest_and_handle(io, interest, Handle::current())
    }

    pub(crate) fn new_with_interest_and_handle(
        mut io: E,
        interest: Interest,
        handle: Handle,
    ) -> io::Result<Self> {
        let registration = Registration::new_with_interest_and_handle(&mut io, interest, handle)?;
        Ok(Self {
            io: Some(io),
            registration,
        })
    }

    /// Returns a reference to the registration.
    #[cfg(any(
        feature = "net",
        all(unix, feature = "process"),
        all(unix, feature = "signal"),
    ))]
    pub(crate) fn registration(&self) -> &Registration {
        &self.registration
    }

    /// Deregisters the inner io from the registration and returns a Result containing the inner io.
    #[cfg(any(feature = "net", feature = "process"))]
    pub(crate) fn into_inner(mut self) -> io::Result<E> {
        let mut inner = self.io.take().unwrap(); // As io shouldn't ever be None, just unwrap here.
        self.registration.deregister(&mut inner)?;
        Ok(inner)
    }
}

feature! {
    #![any(feature = "net", all(unix, feature = "process"))]

    use crate::io::ReadBuf;
    use std::task::{Context, Poll};

    impl<E: Source> PollEvented<E> {
        // Safety: The caller must ensure that `E` can read into uninitialized memory
        pub(crate) unsafe fn poll_read<'a>(
            &'a self,
            cx: &mut Context<'_>,
            buf: &mut ReadBuf<'_>,
        ) -> Poll<io::Result<()>>
        where
            &'a E: io::Read + 'a,
        {
            use std::io::Read;

            loop {
                let evt = ready!(self.registration.poll_read_ready(cx))?;

                let b = &mut *(buf.unfilled_mut() as *mut [std::mem::MaybeUninit<u8>] as *mut [u8]);
                let len = b.len();

                match self.io.as_ref().unwrap().read(b) {
                    Ok(n) => {
                        // if we read a partially full buffer, this is sufficient on unix to show
                        // that the socket buffer has been drained
                        if n > 0 && (!cfg!(windows) && n < len) {
                            self.registration.clear_readiness(evt);
                        }

                        // Safety: We trust `TcpStream::read` to have filled up `n` bytes in the
                        // buffer.
                        buf.assume_init(n);
                        buf.advance(n);
                        return Poll::Ready(Ok(()));
                    },
                    Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
                        self.registration.clear_readiness(evt);
                    }
                    Err(e) => return Poll::Ready(Err(e)),
                }
            }
        }

        pub(crate) fn poll_write<'a>(&'a self, cx: &mut Context<'_>, buf: &[u8]) -> Poll<io::Result<usize>>
        where
            &'a E: io::Write + 'a,
        {
            use std::io::Write;
            self.registration.poll_write_io(cx, || self.io.as_ref().unwrap().write(buf))
        }

        #[cfg(feature = "net")]
        pub(crate) fn poll_write_vectored<'a>(
            &'a self,
            cx: &mut Context<'_>,
            bufs: &[io::IoSlice<'_>],
        ) -> Poll<io::Result<usize>>
        where
            &'a E: io::Write + 'a,
        {
            use std::io::Write;
            self.registration.poll_write_io(cx, || self.io.as_ref().unwrap().write_vectored(bufs))
        }
    }
}

impl<E: Source> UnwindSafe for PollEvented<E> {}

impl<E: Source> RefUnwindSafe for PollEvented<E> {}

impl<E: Source> Deref for PollEvented<E> {
    type Target = E;

    fn deref(&self) -> &E {
        self.io.as_ref().unwrap()
    }
}

impl<E: Source + fmt::Debug> fmt::Debug for PollEvented<E> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("PollEvented").field("io", &self.io).finish()
    }
}

impl<E: Source> Drop for PollEvented<E> {
    fn drop(&mut self) {
        if let Some(mut io) = self.io.take() {
            // Ignore errors
            let _ = self.registration.deregister(&mut io);
        }
    }
}
<commit_msg>io: remove `PollEvented` docs reference to `clear_read_ready` (#4931)<commit_after>use crate::io::driver::{Handle, Interest, Registration};

use mio::event::Source;
use std::fmt;
use std::io;
use std::ops::Deref;
use std::panic::{RefUnwindSafe, UnwindSafe};

cfg_io_driver! {
    /// Associates an I/O resource that implements the [`std::io::Read`] and/or
    /// [`std::io::Write`] traits with the reactor that drives it.
    ///
    /// `PollEvented` uses [`Registration`] internally to take a type that
    /// implements [`mio::event::Source`] as well as [`std::io::Read`] and/or
    /// [`std::io::Write`] and associate it with a reactor that will drive it.
    ///
    /// Once the [`mio::event::Source`] type is wrapped by `PollEvented`, it can be
    /// used from within the future's execution model. As such, the
    /// `PollEvented` type provides [`AsyncRead`] and [`AsyncWrite`]
    /// implementations using the underlying I/O resource as well as readiness
    /// events provided by the reactor.
    ///
    /// **Note**: While `PollEvented` is `Sync` (if the underlying I/O type is
    /// `Sync`), the caller must ensure that there are at most two tasks that
    /// use a `PollEvented` instance concurrently. One for reading and one for
    /// writing. While violating this requirement is "safe" from a Rust memory
    /// model point of view, it will result in unexpected behavior in the form
    /// of lost notifications and tasks hanging.
    ///
    /// ## Readiness events
    ///
    /// Besides just providing [`AsyncRead`] and [`AsyncWrite`] implementations,
    /// this type also supports access to the underlying readiness event stream.
    /// While similar in function to what [`Registration`] provides, the
    /// semantics are a bit different.
    ///
    /// Two functions are provided to access the readiness events:
    /// [`poll_read_ready`] and [`poll_write_ready`]. These functions return the
    /// current readiness state of the `PollEvented` instance. If
    /// [`poll_read_ready`] indicates read readiness, immediately calling
    /// [`poll_read_ready`] again will also indicate read readiness.
    ///
    /// When the operation is attempted and is unable to succeed due to the I/O
    /// resource not being ready, the caller must call [`clear_readiness`].
    /// This clears the readiness state until a new readiness event is received.
    ///
    /// This allows the caller to implement additional functions. For example,
    /// [`TcpListener`] implements poll_accept by using [`poll_read_ready`] and
    /// [`clear_readiness`].
    ///
    /// ## Platform-specific events
    ///
    /// `PollEvented` also allows receiving platform-specific `mio::Ready` events.
    /// These events are included as part of the read readiness event stream. The
    /// write readiness event stream is only for `Ready::writable()` events.
    ///
    /// [`AsyncRead`]: crate::io::AsyncRead
    /// [`AsyncWrite`]: crate::io::AsyncWrite
    /// [`TcpListener`]: crate::net::TcpListener
    /// [`clear_readiness`]: Registration::clear_readiness
    /// [`poll_read_ready`]: Registration::poll_read_ready
    /// [`poll_write_ready`]: Registration::poll_write_ready
    pub(crate) struct PollEvented<E: Source> {
        io: Option<E>,
        registration: Registration,
    }
}

// ===== impl PollEvented =====

impl<E: Source> PollEvented<E> {
    /// Creates a new `PollEvented` associated with the default reactor.
    ///
    /// # Panics
    ///
    /// This function panics if thread-local runtime is not set.
    ///
    /// The runtime is usually set implicitly when this function is called
    /// from a future driven by a tokio runtime, otherwise runtime can be set
    /// explicitly with [`Runtime::enter`](crate::runtime::Runtime::enter) function.
    #[track_caller]
    #[cfg_attr(feature = "signal", allow(unused))]
    pub(crate) fn new(io: E) -> io::Result<Self> {
        PollEvented::new_with_interest(io, Interest::READABLE | Interest::WRITABLE)
    }

    /// Creates a new `PollEvented` associated with the default reactor, for
    /// specific `Interest` state. `new_with_interest` should be used over `new`
    /// when you need control over the readiness state, such as when a file
    /// descriptor only allows reads. This does not add `hup` or `error` so if
    /// you are interested in those states, you will need to add them to the
    /// readiness state passed to this function.
    ///
    /// # Panics
    ///
    /// This function panics if thread-local runtime is not set.
    ///
    /// The runtime is usually set implicitly when this function is called from
    /// a future driven by a tokio runtime, otherwise runtime can be set
    /// explicitly with [`Runtime::enter`](crate::runtime::Runtime::enter)
    /// function.
    #[track_caller]
    #[cfg_attr(feature = "signal", allow(unused))]
    pub(crate) fn new_with_interest(io: E, interest: Interest) -> io::Result<Self> {
        Self::new_with_interest_and_handle(io, interest, Handle::current())
    }

    pub(crate) fn new_with_interest_and_handle(
        mut io: E,
        interest: Interest,
        handle: Handle,
    ) -> io::Result<Self> {
        let registration = Registration::new_with_interest_and_handle(&mut io, interest, handle)?;
        Ok(Self {
            io: Some(io),
            registration,
        })
    }

    /// Returns a reference to the registration.
    #[cfg(any(
        feature = "net",
        all(unix, feature = "process"),
        all(unix, feature = "signal"),
    ))]
    pub(crate) fn registration(&self) -> &Registration {
        &self.registration
    }

    /// Deregisters the inner io from the registration and returns a Result containing the inner io.
    #[cfg(any(feature = "net", feature = "process"))]
    pub(crate) fn into_inner(mut self) -> io::Result<E> {
        let mut inner = self.io.take().unwrap(); // As io shouldn't ever be None, just unwrap here.
        self.registration.deregister(&mut inner)?;
        Ok(inner)
    }
}

feature! {
    #![any(feature = "net", all(unix, feature = "process"))]

    use crate::io::ReadBuf;
    use std::task::{Context, Poll};

    impl<E: Source> PollEvented<E> {
        // Safety: The caller must ensure that `E` can read into uninitialized memory
        pub(crate) unsafe fn poll_read<'a>(
            &'a self,
            cx: &mut Context<'_>,
            buf: &mut ReadBuf<'_>,
        ) -> Poll<io::Result<()>>
        where
            &'a E: io::Read + 'a,
        {
            use std::io::Read;

            loop {
                let evt = ready!(self.registration.poll_read_ready(cx))?;

                let b = &mut *(buf.unfilled_mut() as *mut [std::mem::MaybeUninit<u8>] as *mut [u8]);
                let len = b.len();

                match self.io.as_ref().unwrap().read(b) {
                    Ok(n) => {
                        // if we read a partially full buffer, this is sufficient on unix to show
                        // that the socket buffer has been drained
                        if n > 0 && (!cfg!(windows) && n < len) {
                            self.registration.clear_readiness(evt);
                        }

                        // Safety: We trust `TcpStream::read` to have filled up `n` bytes in the
                        // buffer.
                        buf.assume_init(n);
                        buf.advance(n);
                        return Poll::Ready(Ok(()));
                    },
                    Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
                        self.registration.clear_readiness(evt);
                    }
                    Err(e) => return Poll::Ready(Err(e)),
                }
            }
        }

        pub(crate) fn poll_write<'a>(&'a self, cx: &mut Context<'_>, buf: &[u8]) -> Poll<io::Result<usize>>
        where
            &'a E: io::Write + 'a,
        {
            use std::io::Write;
            self.registration.poll_write_io(cx, || self.io.as_ref().unwrap().write(buf))
        }

        #[cfg(feature = "net")]
        pub(crate) fn poll_write_vectored<'a>(
            &'a self,
            cx: &mut Context<'_>,
            bufs: &[io::IoSlice<'_>],
        ) -> Poll<io::Result<usize>>
        where
            &'a E: io::Write + 'a,
        {
            use std::io::Write;
            self.registration.poll_write_io(cx, || self.io.as_ref().unwrap().write_vectored(bufs))
        }
    }
}

impl<E: Source> UnwindSafe for PollEvented<E> {}

impl<E: Source> RefUnwindSafe for PollEvented<E> {}

impl<E: Source> Deref for PollEvented<E> {
    type Target = E;

    fn deref(&self) -> &E {
        self.io.as_ref().unwrap()
    }
}

impl<E: Source + fmt::Debug> fmt::Debug for PollEvented<E> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("PollEvented").field("io", &self.io).finish()
    }
}

impl<E: Source> Drop for PollEvented<E> {
    fn drop(&mut self) {
        if let Some(mut io) = self.io.take() {
            // Ignore errors
            let _ = self.registration.deregister(&mut io);
        }
    }
}
<|endoftext|>


<commit_before>#![allow(dead_code)]
use std::env;
use std::fs::{self, File};
use std::io;
use std::io::prelude::*;
use std::path::Path;
use std::process::{Command, Stdio};
use std::sync::RwLock;
use std::sync::atomic::{AtomicUsize, Ordering};

use tempdir::TempDir;

use gpgme::{self, Context, Data};
use gpgme::PassphraseRequest;

#[macro_export]
macro_rules! fail_if_err {
    ($e:expr) => (match $e {
        Ok(v) => v,
        Err(err) => panic!("Operation failed: {}", err),
    });
}

macro_rules! count {
    () => {0usize};
    ($_head:tt $($tail:tt)*) => {1usize + count!($($tail)*)};
}

macro_rules! test_case {
    (@impl $name:ident($tester:ident) $body:block,) => {
        #[test]
        fn $name() {
            let $tester = TEST_CASE.new_test();
            $body
        }
    };
    (@impl $name:ident($tester:ident) $body:block,
     $($rest_name:ident($rest_tester:ident) $rest_body:block,)+) => {
        test_case!(@impl $name($tester) $body,);
        test_case!(@impl $($rest_name($rest_tester) $rest_body,)+);
    };
    ($($name:ident($tester:ident) $body:block,)+) => {
        lazy_static! {
            static ref TEST_CASE: $crate::support::TestCase =
                $crate::support::TestCase::new(count!($($name)+));
        }
        test_case!(@impl $($name($tester) $body,)+);
    };
    ($($name:ident($tester:ident) $body:block),+) => {
        test_case!($($name($tester) $body,)+);
    };
}

const KEYS: [(&'static str, &'static [u8]); 5] = [("13CD0F3BDF24BE53FE192D62F18737256FF6E4FD",
                                                   include_bytes!("./data/13CD0F3BDF24BE53FE192D62F18737256FF6E4FD")),
                                                  ("76F7E2B35832976B50A27A282D9B87E44577EB66",
                                                   include_bytes!("./data/76F7E2B35832976B50A27A282D9B87E44577EB66")),
                                                  ("A0747D5F9425E6664F4FFBEED20FBCA79FDED2BD",
                                                   include_bytes!("./data/A0747D5F9425E6664F4FFBEED20FBCA79FDED2BD")),
                                                  ("13CBE3758AFE42B5E5E2AE4CED27AFA455E3F87F",
                                                   include_bytes!("./data/13CBE3758AFE42B5E5E2AE4CED27AFA455E3F87F")),
                                                  ("7A030357C0F253A5BBCD282FFC4E521B37558F5C",
                                                   include_bytes!("./data/7A030357C0F253A5BBCD282FFC4E521B37558F5C"))];

pub fn passphrase_cb(_req: PassphraseRequest, out: &mut Write) -> gpgme::Result<()> {
    try!(out.write_all(b"abc"));
    Ok(())
}

fn create_keys(dir: &Path) {
    let keydir = dir.join("private-keys-v1.d");
    fs::create_dir(&keydir).unwrap();
    for &(fpr, key) in &KEYS {
        let mut filename = keydir.join(fpr);
        filename.set_extension("key");
        File::create(filename).unwrap().write_all(key).unwrap();
    }
}

fn import_key(key: &[u8]) {
    let gpg = env::var_os("GPG").unwrap_or("gpg".into());
    let mut child = Command::new(&gpg)
        .arg("--no-permission-warning")
        .arg("--import")
        .stdin(Stdio::piped())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .spawn()
        .unwrap();
    child.stdin.as_mut().unwrap().write_all(key).unwrap();
    assert!(child.wait().unwrap().success());
}

pub fn setup_agent(dir: &Path) {
    env::set_var("GNUPGHOME", dir);
    env::set_var("GPG_AGENT_INFO", "");
    let mut pinentry = env::current_exe().unwrap();
    pinentry.pop();
    pinentry.push("pinentry");
    pinentry.set_extension(env::consts::EXE_EXTENSION);

    let agent_conf = dir.join("gpg-agent.conf");
    let mut agent_conf = File::create(agent_conf).unwrap();
    agent_conf.write_all(b"ignore-invalid-option allow-loopback-pinentry\n").unwrap();
    agent_conf.write_all(b"allow-loopback-pinentry\n").unwrap();
    agent_conf.write_all(b"ignore-invalid-option pinentry-mode\n").unwrap();
    agent_conf.write_all(b"pinentry-mode loopback\n").unwrap();
    agent_conf.write_all(b"pinentry-program ").unwrap();
    agent_conf.write_all(pinentry.to_str().unwrap().as_ref()).unwrap();
    agent_conf.write_all(b"\n").unwrap();
}

pub struct TestCase {
    count: AtomicUsize,
    homedir: RwLock<Option<TempDir>>,
}

impl TestCase {
    pub fn new(count: usize) -> TestCase {
        let dir = TempDir::new(".test-gpgme").unwrap();
        setup_agent(dir.path());
        create_keys(dir.path());
        import_key(include_bytes!("./data/pubdemo.asc"));
        import_key(include_bytes!("./data/secdemo.asc"));
        println!("count: {}", count);
        TestCase {
            count: AtomicUsize::new(count),
            homedir: RwLock::new(Some(dir)),
        }
    }

    pub fn new_test(&self) -> Test {
        Test { parent: self }
    }

    pub fn kill_agent(&self) {
        let socket = {
            let homedir = self.homedir.read().unwrap();
            homedir.as_ref().unwrap().path().join("S.gpg-agent")
        };
        let mut child = match Command::new("gpg-connect-agent")
            .arg("-S")
            .arg(socket)
            .stdin(Stdio::piped())
            .stdout(Stdio::null())
            .stderr(Stdio::null())
            .spawn() {
            Ok(child) => child,
            Err(_) => return,
        };
        if let Some(ref mut stdin) = child.stdin {
            let _ = stdin.write_all(b"KILLAGENT\nBYE\n");
            let _ = stdin.flush();
        }
        let _ = child.wait();
    }

    fn drop(&self) {
        if self.count.fetch_sub(1, Ordering::SeqCst) == 1 {
            self.kill_agent();
            if let Some(dir) = self.homedir.write().unwrap().take() {
                drop(dir);
            }
        }
    }
}

pub struct Test<'a> {
    parent: &'a TestCase,
}

impl<'a> Drop for Test<'a> {
    fn drop(&mut self) {
        self.parent.drop();
    }
}

impl<'a> Test<'a> {
    pub fn create_context(&self) -> Context {
        fail_if_err!(Context::from_protocol(gpgme::Protocol::OpenPgp))
    }

    pub fn check_data(data: &mut Data, expected: &[u8]) {
        let mut buffer = Vec::new();
        data.seek(io::SeekFrom::Start(0)).unwrap();
        data.read_to_end(&mut buffer).unwrap();
        assert_eq!(buffer, expected);
    }
}
<commit_msg>Fix tests on nightly<commit_after>#![allow(dead_code)]
use std::env;
use std::fs::{self, File};
use std::io;
use std::io::prelude::*;
use std::path::Path;
use std::process::{Command, Stdio};
use std::sync::RwLock;
use std::sync::atomic::{AtomicUsize, Ordering};

use tempdir::TempDir;

use gpgme::{self, Context, Data};
use gpgme::PassphraseRequest;

#[macro_export]
macro_rules! fail_if_err {
    ($e:expr) => (match $e {
        Ok(v) => v,
        Err(err) => panic!("Operation failed: {}", err),
    });
}

macro_rules! count {
    () => {0usize};
    ($_head:tt $($tail:tt)*) => {1usize + count!($($tail)*)};
}

macro_rules! test_case {
    (@impl $name:ident($tester:ident) $body:block,) => {
        #[test]
        fn $name() {
            let $tester = TEST_CASE.new_test();
            $body
        }
    };
    (@impl $name:ident($tester:ident) $body:block,
     $($rest_name:ident($rest_tester:ident) $rest_body:block,)+) => {
        test_case!(@impl $name($tester) $body,);
        test_case!(@impl $($rest_name($rest_tester) $rest_body,)+);
    };
    ($($name:ident($tester:ident) $body:block,)+) => {
        lazy_static! {
            static ref TEST_CASE: $crate::support::TestCase =
                $crate::support::TestCase::new(count!($($name)+));
        }
        test_case!(@impl $($name($tester) $body,)+);
    };
    ($($name:ident($tester:ident) $body:block),+) => {
        test_case!($($name($tester) $body,)+);
    };
}

const KEYS: [(&'static str, &'static [u8]); 5] = [("13CD0F3BDF24BE53FE192D62F18737256FF6E4FD",
                                                   include_bytes!("./data/13CD0F3BDF24BE53FE192D62F18737256FF6E4FD")),
                                                  ("76F7E2B35832976B50A27A282D9B87E44577EB66",
                                                   include_bytes!("./data/76F7E2B35832976B50A27A282D9B87E44577EB66")),
                                                  ("A0747D5F9425E6664F4FFBEED20FBCA79FDED2BD",
                                                   include_bytes!("./data/A0747D5F9425E6664F4FFBEED20FBCA79FDED2BD")),
                                                  ("13CBE3758AFE42B5E5E2AE4CED27AFA455E3F87F",
                                                   include_bytes!("./data/13CBE3758AFE42B5E5E2AE4CED27AFA455E3F87F")),
                                                  ("7A030357C0F253A5BBCD282FFC4E521B37558F5C",
                                                   include_bytes!("./data/7A030357C0F253A5BBCD282FFC4E521B37558F5C"))];

pub fn passphrase_cb(_req: PassphraseRequest, out: &mut Write) -> gpgme::Result<()> {
    try!(out.write_all(b"abc"));
    Ok(())
}

fn create_keys(dir: &Path) {
    let keydir = dir.join("private-keys-v1.d");
    fs::create_dir(&keydir).unwrap();
    for &(fpr, key) in &KEYS {
        let mut filename = keydir.join(fpr);
        filename.set_extension("key");
        File::create(filename).unwrap().write_all(key).unwrap();
    }
}

fn import_key(key: &[u8]) {
    let gpg = env::var_os("GPG").unwrap_or("gpg".into());
    let mut child = Command::new(&gpg)
        .arg("--no-permission-warning")
        .arg("--import")
        .stdin(Stdio::piped())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .spawn()
        .unwrap();
    child.stdin.as_mut().unwrap().write_all(key).unwrap();
    assert!(child.wait().unwrap().success());
}

pub fn setup_agent(dir: &Path) {
    env::set_var("GNUPGHOME", dir);
    env::set_var("GPG_AGENT_INFO", "");
    let pinentry = {
        let mut path = env::current_exe().unwrap();
        path.pop();
        if path.ends_with("deps") {
            path.pop();
        }
        path.push("pinentry");
        path.set_extension(env::consts::EXE_EXTENSION);
        path
    };
    if !pinentry.exists() {
        panic!("Unable to find pinentry program");
    }

    let agent_conf = dir.join("gpg-agent.conf");
    let mut agent_conf = File::create(agent_conf).unwrap();
    agent_conf.write_all(b"ignore-invalid-option allow-loopback-pinentry\n").unwrap();
    agent_conf.write_all(b"allow-loopback-pinentry\n").unwrap();
    agent_conf.write_all(b"ignore-invalid-option pinentry-mode\n").unwrap();
    agent_conf.write_all(b"pinentry-mode loopback\n").unwrap();
    agent_conf.write_all(b"pinentry-program ").unwrap();
    agent_conf.write_all(pinentry.to_str().unwrap().as_ref()).unwrap();
    agent_conf.write_all(b"\n").unwrap();
}

pub struct TestCase {
    count: AtomicUsize,
    homedir: RwLock<Option<TempDir>>,
}

impl TestCase {
    pub fn new(count: usize) -> TestCase {
        let dir = TempDir::new(".test-gpgme").unwrap();
        setup_agent(dir.path());
        create_keys(dir.path());
        import_key(include_bytes!("./data/pubdemo.asc"));
        import_key(include_bytes!("./data/secdemo.asc"));
        TestCase {
            count: AtomicUsize::new(count),
            homedir: RwLock::new(Some(dir)),
        }
    }

    pub fn new_test(&self) -> Test {
        Test { parent: self }
    }

    pub fn kill_agent(&self) {
        let socket = {
            let homedir = self.homedir.read().unwrap();
            homedir.as_ref().unwrap().path().join("S.gpg-agent")
        };
        let mut child = match Command::new("gpg-connect-agent")
            .arg("-S")
            .arg(socket)
            .stdin(Stdio::piped())
            .stdout(Stdio::null())
            .stderr(Stdio::null())
            .spawn() {
            Ok(child) => child,
            Err(_) => return,
        };
        if let Some(ref mut stdin) = child.stdin {
            let _ = stdin.write_all(b"KILLAGENT\nBYE\n");
            let _ = stdin.flush();
        }
        let _ = child.wait();
    }

    fn drop(&self) {
        if self.count.fetch_sub(1, Ordering::SeqCst) == 1 {
            self.kill_agent();
            if let Some(dir) = self.homedir.write().unwrap().take() {
                drop(dir);
            }
        }
    }
}

pub struct Test<'a> {
    parent: &'a TestCase,
}

impl<'a> Drop for Test<'a> {
    fn drop(&mut self) {
        self.parent.drop();
    }
}

impl<'a> Test<'a> {
    pub fn create_context(&self) -> Context {
        fail_if_err!(Context::from_protocol(gpgme::Protocol::OpenPgp))
    }

    pub fn check_data(data: &mut Data, expected: &[u8]) {
        let mut buffer = Vec::new();
        data.seek(io::SeekFrom::Start(0)).unwrap();
        data.read_to_end(&mut buffer).unwrap();
        assert_eq!(buffer, expected);
    }
}
<|endoftext|>


<commit_before>use std::{
    cell::Cell,
    collections::{HashMap, HashSet},
};

use super::behaviors::MouseBehavior;
use crate::{prelude::*, shell::WindowRequest, utils::SelectionMode as SelMode};

static ITEMS_PANEL: &'static str = "items_panel";

/// The `ListViewState` generates the list box items and handles the selected indices.
#[derive(Default, AsAny)]
pub struct ListViewState {
    builder: WidgetBuildContext,
    count: usize,
    selected_entities: RefCell<HashSet<Entity>>,
    items_panel: Entity,
}

impl State for ListViewState {
    fn init(&mut self, _: &mut Registry, ctx: &mut Context) {
        self.items_panel = ctx
            .entity_of_child(ITEMS_PANEL)
            .expect("ListViewState.init: ItemsPanel child could not be found.");
    }

    fn update(&mut self, _: &mut Registry, ctx: &mut Context) {
        let count = ctx.widget().clone_or_default::<usize>("count");
        let entity = ctx.entity;

        if count != self.count {
            if let Some(builder) = &self.builder {
                ctx.clear_children_of(self.items_panel);

                for i in 0..count {
                    let item = {
                        let build_context = &mut ctx.build_context();
                        let child = builder(build_context, i);
                        let item = ListViewItem::new().parent(entity.0).build(build_context);

                        let mouse_behavior = MouseBehavior::new().build(build_context);
                        build_context.register_shared_property::<Selector>(
                            "selector",
                            mouse_behavior,
                            item,
                        );
                        build_context.register_shared_property::<bool>(
                            "pressed",
                            mouse_behavior,
                            item,
                        );
                        build_context.append_child(item, mouse_behavior);

                        build_context.register_shared_property::<Brush>("foreground", child, item);
                        build_context.register_shared_property::<f32>("opacity", item, entity);
                        build_context.register_shared_property::<f32>("opacity", child, entity);
                        build_context.register_shared_property::<f64>("font_size", child, item);
                        build_context.append_child(self.items_panel, item);
                        build_context.append_child(mouse_behavior, child);

                        item
                    };
                    ctx.get_widget(item).update_widget(entity, false);
                }
            }

            self.count = count;
        }
    }

    fn update_post_layout(&mut self, _: &mut Registry, ctx: &mut Context) {
        for index in ctx
            .widget()
            .get::<SelectedEntities>("selected_entities")
            .0
            .clone()
            .symmetric_difference(&*self.selected_entities.borrow())
        {
            let mut widget = ctx.get_widget(*index);

            if !widget.has::<bool>("selected") {
                continue;
            }

            let selected = !widget.get::<bool>("selected");
            widget.set("selected", selected);

            if selected {
                widget.get_mut::<Selector>("selector").set_state("selected");
            } else {
                widget.get_mut::<Selector>("selector").clear_state();
            }

            widget.update(false);
        }

        *self.selected_entities.borrow_mut() = ctx
            .widget()
            .get::<SelectedEntities>("selected_entities")
            .0
            .clone();
    }
}

/// The `ListViewItemState` handles the interaction and selection of a `ListViewItem`.
#[derive(Default, AsAny)]
pub struct ListViewItemState {
    request_selection_toggle: Cell<bool>,
}

impl ListViewItemState {
    fn toggle_selection(&self) {
        self.request_selection_toggle.set(true);
    }
}

impl State for ListViewItemState {
    fn update(&mut self, _: &mut Registry, ctx: &mut Context) {
        if !ctx.widget().get::<bool>("enabled") || !self.request_selection_toggle.get() {
            return;
        }
        self.request_selection_toggle.set(false);

        let selected = *ctx.widget().get::<bool>("selected");

        let entity = ctx.entity;
        let index = ctx.index_as_child(entity).unwrap();

        let parent_entity: Entity = (*ctx.widget().get::<u32>("parent")).into();

        let mut parent = ctx.get_widget(parent_entity);

        let selection_mode = *parent.get::<SelectionMode>("selection_mode");
        // deselect item
        if selected {
            parent
                .get_mut::<SelectedEntities>("selected_entities")
                .0
                .remove(&entity);
            parent
                .get_mut::<SelectedIndices>("selected_indices")
                .0
                .remove(&index);
            return;
        }

        if parent
            .get::<SelectedEntities>("selected_entities")
            .0
            .contains(&entity)
            || selection_mode == SelMode::None
        {
            return;
        }

        if selection_mode == SelMode::Single {
            parent
                .get_mut::<SelectedEntities>("selected_entities")
                .0
                .clear();
            parent
                .get_mut::<SelectedIndices>("selected_indices")
                .0
                .clear();
        }

        parent
            .get_mut::<SelectedEntities>("selected_entities")
            .0
            .insert(entity);
        parent
            .get_mut::<SelectedIndices>("selected_indices")
            .0
            .insert(index);

        let selected_indices: Vec<usize> = parent
            .get::<SelectedIndices>("selected_indices")
            .0
            .iter()
            .map(|i| *i)
            .collect();

        ctx.push_event_strategy_by_entity(
            SelectionChangedEvent(parent_entity, selected_indices),
            parent_entity,
            EventStrategy::Direct,
        );

        ctx.send_window_request(WindowRequest::Redraw);
    }
}

widget!(
    /// The `ListViewItem` describes an item inside of a `ListView`.
    ///
    /// **CSS element:** `list-view``
    ListViewItem<ListViewItemState>: MouseHandler {
        /// Sets or shares the background property.
        background: Brush,

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

        /// Sets or shares the border thickness property.
        border_width: Thickness,

        /// Sets or shares the border brush property.
        border_brush: Brush,

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

        /// Sets or share the font size property.
        font_size: f64,

        /// Sets or shares the font property.
        font: String,

        /// Sets or shares the padding property.
        padding: Thickness,

        /// Sets or shares the pressed property.
        pressed: bool,

        /// Sets or shares the selected property.
        selected: bool,

        /// Sets or shares the parent id.
        parent: u32
    }
);

impl Template for ListViewItem {
    fn template(self, id: Entity, ctx: &mut BuildContext) -> Self {
        self.name("ListViewItem")
            .style("list_view_item")
            .min_width(64.0)
            .height(24.0)
            .selected(false)
            .pressed(false)
            .padding(0.0)
            .background("transparent")
            .border_radius(0.0)
            .border_width(0.0)
            .border_brush("transparent")
            .foreground(colors::LINK_WATER_COLOR)
            .font_size(32.0)
            .font("Roboto Regular")
            .on_click(move |states, _| {
                states.get::<ListViewItemState>(id).toggle_selection();
                false
            })
            .child(
                MouseBehavior::new()
                    .pressed(id)
                    .enabled(id)
                    .target(id.0)
                    .build(ctx),
            )
    }

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

    fn layout(&self) -> Box<dyn Layout> {
        Box::new(PaddingLayout::new())
    }
}

widget!(
    /// The `ListView` is an items drawer widget with selectable items.
    ///
    /// **CSS element:** `items-widget`
    ListView<ListViewState> : SelectionChangedHandler {
        /// Sets or shares the background property.
        background: Brush,

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

        /// Sets or shares the border thickness property.
        border_width: Thickness,

        /// Sets or shares the border brush property.
        border_brush: Brush,

        /// Sets or shares the padding property.
        padding: Thickness,

        /// Sets or shares the orientation property.
        orientation: Orientation,

        /// Sets or shares the item count.
        count: usize,

        /// Sets or shares the selection mode property.
        selection_mode: SelectionMode,

        /// Sets or shares the selected indices.
        selected_indices: SelectedIndices,

        /// Sets or shares the list of selected indices.
        selected_entities: SelectedEntities,

        /// Sets or shares the (wheel, scroll) delta property.
        delta: Point
    }
);

impl ListView {
    /// Define the template build function for the content of the ListViewItems.
    pub fn items_builder<F: Fn(&mut BuildContext, usize) -> Entity + 'static>(
        mut self,
        builder: F,
    ) -> Self {
        self.state_mut().builder = Some(Box::new(builder));
        self
    }
}

impl Template for ListView {
    fn template(self, id: Entity, ctx: &mut BuildContext) -> Self {
        let items_panel = Stack::new()
            .v_align("start")
            .id(ITEMS_PANEL)
            .orientation(id)
            .build(ctx);

        let scroll_viewer = ScrollViewer::new()
            .scroll_viewer_mode(("disabled", "auto"))
            .delta(id)
            .child(items_panel)
            .build(ctx);

        self.name("ListView")
            .style("list_view")
            .background(colors::LYNCH_COLOR)
            .border_radius(2.0)
            .border_width(1.0)
            .border_brush(colors::BOMBAY_COLOR)
            .padding(2.0)
            .selection_mode("single")
            .selected_indices(HashSet::new())
            .selected_entities(HashSet::new())
            .delta(0.0)
            .orientation("vertical")
            .child(
                Container::new()
                    .background(id)
                    .border_radius(id)
                    .border_width(id)
                    .border_brush(id)
                    .padding(id)
                    .opacity(id)
                    .child(scroll_viewer)
                    .child(
                        ScrollIndicator::new()
                            .padding(2.0)
                            .content_id(items_panel.0)
                            .scroll_offset(scroll_viewer)
                            .opacity(id)
                            .build(ctx),
                    )
                    .build(ctx),
            )
    }
}
<commit_msg>update.<commit_after>use std::{
    cell::Cell,
    collections::{HashMap, HashSet},
};

use super::behaviors::MouseBehavior;
use crate::{prelude::*, utils::SelectionMode as SelMode};

static ITEMS_PANEL: &'static str = "items_panel";

/// The `ListViewState` generates the list box items and handles the selected indices.
#[derive(Default, AsAny)]
pub struct ListViewState {
    builder: WidgetBuildContext,
    count: usize,
    selected_entities: RefCell<HashSet<Entity>>,
    items_panel: Entity,
}

impl State for ListViewState {
    fn init(&mut self, _: &mut Registry, ctx: &mut Context) {
        self.items_panel = ctx
            .entity_of_child(ITEMS_PANEL)
            .expect("ListViewState.init: ItemsPanel child could not be found.");
    }

    fn update(&mut self, _: &mut Registry, ctx: &mut Context) {
        let count = ctx.widget().clone_or_default::<usize>("count");
        let entity = ctx.entity;

        if count != self.count {
            if let Some(builder) = &self.builder {
                ctx.clear_children_of(self.items_panel);

                for i in 0..count {
                    let item = {
                        let build_context = &mut ctx.build_context();
                        let child = builder(build_context, i);
                        let item = ListViewItem::new().parent(entity.0).build(build_context);

                        let mouse_behavior = MouseBehavior::new().build(build_context);
                        build_context.register_shared_property::<Selector>(
                            "selector",
                            mouse_behavior,
                            item,
                        );
                        build_context.register_shared_property::<bool>(
                            "pressed",
                            mouse_behavior,
                            item,
                        );
                        build_context.append_child(item, mouse_behavior);

                        build_context.register_shared_property::<Brush>("foreground", child, item);
                        build_context.register_shared_property::<f32>("opacity", item, entity);
                        build_context.register_shared_property::<f32>("opacity", child, entity);
                        build_context.register_shared_property::<f64>("font_size", child, item);
                        build_context.append_child(self.items_panel, item);
                        build_context.append_child(mouse_behavior, child);

                        item
                    };
                    ctx.get_widget(item).update_widget(entity, false);
                }
            }

            self.count = count;
        }
    }

    fn update_post_layout(&mut self, _: &mut Registry, ctx: &mut Context) {
        for index in ctx
            .widget()
            .get::<SelectedEntities>("selected_entities")
            .0
            .clone()
            .symmetric_difference(&*self.selected_entities.borrow())
        {
            let mut widget = ctx.get_widget(*index);

            if !widget.has::<bool>("selected") {
                continue;
            }

            let selected = !widget.get::<bool>("selected");
            widget.set("selected", selected);

            if selected {
                widget.get_mut::<Selector>("selector").set_state("selected");
            } else {
                widget.get_mut::<Selector>("selector").clear_state();
            }

            widget.update(false);
        }

        *self.selected_entities.borrow_mut() = ctx
            .widget()
            .get::<SelectedEntities>("selected_entities")
            .0
            .clone();
    }
}

/// The `ListViewItemState` handles the interaction and selection of a `ListViewItem`.
#[derive(Default, AsAny)]
pub struct ListViewItemState {
    request_selection_toggle: Cell<bool>,
}

impl ListViewItemState {
    fn toggle_selection(&self) {
        self.request_selection_toggle.set(true);
    }
}

impl State for ListViewItemState {
    fn update(&mut self, _: &mut Registry, ctx: &mut Context) {
        if !ctx.widget().get::<bool>("enabled") || !self.request_selection_toggle.get() {
            return;
        }
        self.request_selection_toggle.set(false);

        let selected = *ctx.widget().get::<bool>("selected");

        let entity = ctx.entity;
        let index = ctx.index_as_child(entity).unwrap();

        let parent_entity: Entity = (*ctx.widget().get::<u32>("parent")).into();

        let mut parent = ctx.get_widget(parent_entity);

        let selection_mode = *parent.get::<SelectionMode>("selection_mode");
        // deselect item
        if selected {
            parent
                .get_mut::<SelectedEntities>("selected_entities")
                .0
                .remove(&entity);
            parent
                .get_mut::<SelectedIndices>("selected_indices")
                .0
                .remove(&index);
            return;
        }

        if parent
            .get::<SelectedEntities>("selected_entities")
            .0
            .contains(&entity)
            || selection_mode == SelMode::None
        {
            return;
        }

        if selection_mode == SelMode::Single {
            parent
                .get_mut::<SelectedEntities>("selected_entities")
                .0
                .clear();
            parent
                .get_mut::<SelectedIndices>("selected_indices")
                .0
                .clear();
        }

        parent
            .get_mut::<SelectedEntities>("selected_entities")
            .0
            .insert(entity);
        parent
            .get_mut::<SelectedIndices>("selected_indices")
            .0
            .insert(index);

        let selected_indices: Vec<usize> = parent
            .get::<SelectedIndices>("selected_indices")
            .0
            .iter()
            .map(|i| *i)
            .collect();

        ctx.push_event_strategy_by_entity(
            SelectionChangedEvent(parent_entity, selected_indices),
            parent_entity,
            EventStrategy::Direct,
        );
    }
}

widget!(
    /// The `ListViewItem` describes an item inside of a `ListView`.
    ///
    /// **CSS element:** `list-view``
    ListViewItem<ListViewItemState>: MouseHandler {
        /// Sets or shares the background property.
        background: Brush,

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

        /// Sets or shares the border thickness property.
        border_width: Thickness,

        /// Sets or shares the border brush property.
        border_brush: Brush,

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

        /// Sets or share the font size property.
        font_size: f64,

        /// Sets or shares the font property.
        font: String,

        /// Sets or shares the padding property.
        padding: Thickness,

        /// Sets or shares the pressed property.
        pressed: bool,

        /// Sets or shares the selected property.
        selected: bool,

        /// Sets or shares the parent id.
        parent: u32
    }
);

impl Template for ListViewItem {
    fn template(self, id: Entity, ctx: &mut BuildContext) -> Self {
        self.name("ListViewItem")
            .style("list_view_item")
            .min_width(64.0)
            .height(24.0)
            .selected(false)
            .pressed(false)
            .padding(0.0)
            .background("transparent")
            .border_radius(0.0)
            .border_width(0.0)
            .border_brush("transparent")
            .foreground(colors::LINK_WATER_COLOR)
            .font_size(32.0)
            .font("Roboto Regular")
            .on_click(move |states, _| {
                states.get::<ListViewItemState>(id).toggle_selection();
                false
            })
            .child(
                MouseBehavior::new()
                    .pressed(id)
                    .enabled(id)
                    .target(id.0)
                    .build(ctx),
            )
    }

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

    fn layout(&self) -> Box<dyn Layout> {
        Box::new(PaddingLayout::new())
    }
}

widget!(
    /// The `ListView` is an items drawer widget with selectable items.
    ///
    /// **CSS element:** `items-widget`
    ListView<ListViewState> : SelectionChangedHandler {
        /// Sets or shares the background property.
        background: Brush,

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

        /// Sets or shares the border thickness property.
        border_width: Thickness,

        /// Sets or shares the border brush property.
        border_brush: Brush,

        /// Sets or shares the padding property.
        padding: Thickness,

        /// Sets or shares the orientation property.
        orientation: Orientation,

        /// Sets or shares the item count.
        count: usize,

        /// Sets or shares the selection mode property.
        selection_mode: SelectionMode,

        /// Sets or shares the selected indices.
        selected_indices: SelectedIndices,

        /// Sets or shares the list of selected indices.
        selected_entities: SelectedEntities,

        /// Sets or shares the (wheel, scroll) delta property.
        delta: Point
    }
);

impl ListView {
    /// Define the template build function for the content of the ListViewItems.
    pub fn items_builder<F: Fn(&mut BuildContext, usize) -> Entity + 'static>(
        mut self,
        builder: F,
    ) -> Self {
        self.state_mut().builder = Some(Box::new(builder));
        self
    }
}

impl Template for ListView {
    fn template(self, id: Entity, ctx: &mut BuildContext) -> Self {
        let items_panel = Stack::new()
            .v_align("start")
            .id(ITEMS_PANEL)
            .orientation(id)
            .build(ctx);

        let scroll_viewer = ScrollViewer::new()
            .scroll_viewer_mode(("disabled", "auto"))
            .delta(id)
            .child(items_panel)
            .build(ctx);

        self.name("ListView")
            .style("list_view")
            .background(colors::LYNCH_COLOR)
            .border_radius(2.0)
            .border_width(1.0)
            .border_brush(colors::BOMBAY_COLOR)
            .padding(2.0)
            .selection_mode("single")
            .selected_indices(HashSet::new())
            .selected_entities(HashSet::new())
            .delta(0.0)
            .orientation("vertical")
            .child(
                Container::new()
                    .background(id)
                    .border_radius(id)
                    .border_width(id)
                    .border_brush(id)
                    .padding(id)
                    .opacity(id)
                    .child(scroll_viewer)
                    .child(
                        ScrollIndicator::new()
                            .padding(2.0)
                            .content_id(items_panel.0)
                            .scroll_offset(scroll_viewer)
                            .opacity(id)
                            .build(ctx),
                    )
                    .build(ctx),
            )
    }
}
<|endoftext|>