<commit_before>//! # Hexdino
//!
//! A hex editor with vim like keybindings written in Rust.

#![doc(html_logo_url = "https://raw.githubusercontent.com/Luz/hexdino/master/logo.png")]

use std::io::prelude::*;
use std::fs::OpenOptions;
use std::io::SeekFrom;
use std::path::Path;
use std::error::Error;
use std::env;

extern crate ncurses;
use ncurses::*;

//extern crate getopts;
//use getopts::Options;

fn main() {
    let mut buf = vec![];
    let mut cursorpos:usize = 0;
    let mut cursorstate:usize = 0; //0 is left nibble, 1 is right nibble, 2 is ascii
    let mut screenoffset:usize = 0; // 0 = display data from first line of file
    const SPALTEN:usize = 16;
    let mut command = String::new();

    initscr(); //start ncursesw
    let screenheight : usize = getmaxy(stdscr) as usize;
    cbreak();  //ctrl+z and fg works with this
    noecho();
    start_color();
    init_pair(1, COLOR_GREEN, COLOR_BLACK);

    printw("Welcome to Hexdino.\nPlease find the any-key on your keyboard and press it.\n");

    let args: Vec<_> = env::args().collect();
    /*
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optopt("f", "", "set file name", "NAME");
    opts.optflag("h", "help", "print this help menu");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m }
        Err(f) => { panic!(f.to_string()) }
    };
    if matches.opt_present("h") {
        let brief = format!("Usage: {} FILE [options]", program);
            print!("{}", opts.usage(&brief));
        return;
    }
    let path = if !matches.free.is_empty() {
        matches.free[0].clone()
    } else {
        let brief = format!("Usage: {} FILE [options]", program);
            print!("{}", opts.usage(&brief));
        return;
    };*/

    let path = if args.len() > 1 {
        printw(&format!("Opening file {}", args[1]));
        args[1].clone()
    } else {
        printw(&format!("No file specified. Trying to open foo.txt"));
        "foo.txt".into()
    };

    let path = Path::new(&path);
    let display = path.display();

    getch();
    clear();

    if !has_colors() {
        endwin();
        println!("Your terminal does not support color!\n");
        return;
    }

//TODO in this order: 1)disp files 2) hjkl movement 3) edit file by 'r' 4) save file 5) edit file by 'x, i' 6) search by '/'

    let mut file = match OpenOptions::new().read(true).write(true).create(true).open(&path) {
        Err(why) => panic!("couldn't open {}: {}", display,
            Error::description(&why)),
        Ok(file) => file,
    };

    file.read_to_end(&mut buf).ok().expect("File could not be read.");

    let mut mode = 0; // 0 Command mode, 1 replace next char, 2 type a command, 3 Insert

    draw(&buf, cursorpos, SPALTEN, screenheight, mode, &command, cursorstate, screenoffset);

    let mut key;
//    key = getch();
//    printw(&format!("{:?}", key));

    let mut quitnow = 0;
    while quitnow == 0 {
        key = getch() as u8;
        if mode == 0 { // movement mode
            match key{
                104 => { // Button "h"
                    if cursorstate == 2 { // ascii mode
                        if cursorpos > 0 { // not at start
                            cursorpos-=1; // go left
                        }
                    }
                    else if cursorstate == 1 { // hex mode, right nibble
                        cursorstate = 0; // left nibble
                    }
                    else if cursorstate == 0 { // hex mode, left nibble
                        if cursorpos > 0 { // not at start
                            cursorstate = 1; // right nibble
                            cursorpos-=1; // go left
                        }
                    }
                },
                106 => { //Button "j"
                    if cursorpos+SPALTEN < buf.len() { // not at end
                        cursorpos+=SPALTEN; // go down
                    }
                    else { // when at end
                        cursorpos=buf.len()-1; // go to end
                    }
                },
                107 => { //Button "k"
                    if cursorpos >= SPALTEN {
                        cursorpos-=SPALTEN;
                    }
                },
                108 => { // Button "l"
                    if cursorstate == 2 { // ascii mode
                        if cursorpos < buf.len()-1 { // not at end
                            cursorpos+=1; // go right
                        }
                    }
                    else if cursorstate == 0 { // hex mode, left nibble
                        cursorstate = 1; // right nibble
                    }
                    else if cursorstate == 1 { // hex mode, right nibble
                        if cursorpos < buf.len()-1 { // not at end
                            cursorstate = 0; // left nibble
                            cursorpos+=1; // go right
                        }
                    }
                },
                 48 => { // Button "0"
                    cursorpos -= cursorpos%SPALTEN; // jump to start of line
                    if cursorstate == 1 { // hex mode, right nibble
                        cursorstate = 0; // left nibble
                    }
                },
                 36 => { // Button "$"
                    if cursorpos-(cursorpos%SPALTEN)+(SPALTEN-1) < buf.len() { // check if no overflow
                        cursorpos = cursorpos-(cursorpos%SPALTEN)+(SPALTEN-1); // jump to end of line
                    }
                    else {
                        cursorpos = buf.len()-1 // jump to end of line
                    }
                    if cursorstate == 0 { // hex mode, left nibble
                        cursorstate = 1; // right nibble
                    }
                },
                114 => { // Button "r"
                    mode = 1; // replaces the next char
                },
                120 => { // Button "x"
                    if buf.len() > 0 { // remove the next char
                        buf.remove(cursorpos);
                    }
                },
                105 => { // Button "i"
                    mode = 3; // go to insert mode
                },
                 58 => { // Button ":"
                     command.clear(); // delete old command
                     mode = 2; // write a command
                 },
                 74 => { // Button "J"
                     if cursorstate == 2 { // ascii mode
                         cursorstate = 0; // hex mode, left nibble
                     }
                     else {
                         cursorstate = 2; // jump to ascii
                     }
                 },
                 63 => { //TODO
                     command.push_str("No helpfile yet");
                 },
                _ => (),
            }

            //Always move screen when cursor leaves screen
            if cursorpos > (screenheight+screenoffset-1)*SPALTEN -1 {
                screenoffset+=1; // move screen
            }
            if cursorpos < screenoffset*SPALTEN {
                screenoffset-=1; // move screen
            }

        } else
        if mode == 1 && cursorstate == 2 { // r was pressed so replace next char in ascii mode
            match key {
                c @ 32...126 => { buf[cursorpos] = c },
                _ => (),
            }
            mode = 0;
        } else
        if mode == 1 {
            let mask = if cursorstate == 0 { 0x0F } else { 0xF0 };
            let shift = if cursorstate == 0 { 4 } else { 0 };
            match key { // Change the selected nibble
                c @ 65... 70 => // A-F
                    { buf[cursorpos] = buf[cursorpos]&mask | (c-55)<<shift },
                c @ 97...102 => // a-f
                    { buf[cursorpos] = buf[cursorpos]&mask | (c-87)<<shift },
                c @ 48... 57 => // 0-9
                    { buf[cursorpos] = buf[cursorpos]&mask | (c-48)<<shift },
                _ => ()
            }
            mode = 0;
        } else
        if mode == 2 {
            match key {
                c @ 32...126 => { command.push(c as char); },
                27 => {command.clear();mode = 0;},
                10 => { // Enter pressed, compute command!
                        if (command == "w".to_string()) || (command == "wq".to_string()) {
                            file.seek(SeekFrom::Start(0)).ok().expect("Filepointer could not be set to 0");
                            file.write_all(&mut buf).ok().expect("File could not be written.");
                            if command == "wq".to_string() {
                                quitnow = 1;
                            }
                            command.clear();mode = 0;
                        }
                        else if command == "q".to_string() {
                            quitnow = 1;
                        }
                        else {
                            command.clear();
                            command.push_str("Bad_command!");
                            mode = 0;
                        }
                    },
                _ => (),
            }
        } else
        if mode == 3 {
            if cursorstate == 0 { // Left nibble
                match key {
                    c @ 65... 70 => // A-F
                        {buf.insert(cursorpos, (c-55)<<4 ); cursorstate = 1;},
                    c @ 97...102 => // a-f
                        {buf.insert(cursorpos, (c-87)<<4 ); cursorstate = 1;},
                    c @ 48... 57 => // 0-9
                        {buf.insert(cursorpos, (c-48)<<4 ); cursorstate = 1;},
                    27 => {mode = 0;},
                    _ => ()
                }
            } else
            if cursorstate == 1 { // Right nibble
                match key {
                    c @ 65...70 => // A-F
                        { buf[cursorpos] = buf[cursorpos]&0xF0 | (c-55); cursorstate = 0; cursorpos+=1; },
                    c @ 97...102 => // a-f
                        { buf[cursorpos] = buf[cursorpos]&0xF0 | (c-87); cursorstate = 0; cursorpos+=1; },
                    c @ 48...57 => // 0-9
                        { buf[cursorpos] = buf[cursorpos]&0xF0 | (c-48); cursorstate = 0; cursorpos+=1; },
                    27 => {mode = 0;},
                    _ => ()
                }
            } else
            if cursorstate == 2 { // Ascii
                match key {
                    c @ 32...126 => { buf.insert(cursorpos, c); cursorpos+=1; },
                    27 => {mode = 0;},
                    _ => (),
                }
            }
        }
        draw(&buf, cursorpos, SPALTEN, screenheight, mode, &command, cursorstate, screenoffset);
    }

    refresh();
    endwin();
}

fn draw(buf:&Vec<u8>, cursorpos:usize, spalten:usize, maxzeilen:usize, mode:usize, command:&String, cursorstate:usize, screenoffset:usize) {
    erase();

    let mut zeilen = buf.len() / spalten;
    if zeilen >= maxzeilen-1 { // Last line reserved for Status/Commands/etc (Like in vim)
        zeilen = maxzeilen-2;
    }

    for z in 0 .. zeilen+1 {
        printw(&format!("{:08X}: ", z*spalten+screenoffset*spalten )); // 8 hex digits (4GB/spalten or 0.25GB@spalten=SPALTEN)
        printw(" "); // Additional space between line number and hex
        for s in 0 .. spalten {
            if z*spalten+screenoffset*spalten+s < buf.len() {
                if z*spalten+screenoffset*spalten+s == cursorpos { // Color of left nibble
                    if cursorstate == 0 {attron(COLOR_PAIR(1) | A_STANDOUT());}
                    else if cursorstate == 2 {attron(A_UNDERLINE());}
                }
                printw(&format!("{:01X}", buf[z*spalten+screenoffset*spalten+s]>>4) ); // Display left nibble
                if z*spalten+screenoffset*spalten+s == cursorpos { // End of color left nibble
                    if cursorstate == 0 {attroff(COLOR_PAIR(1) | A_STANDOUT());}
                    else if cursorstate == 2 {attroff(A_UNDERLINE());}
                }

                if z*spalten+screenoffset*spalten+s == cursorpos { // Color of right nibble
                    if cursorstate == 1 {attron(COLOR_PAIR(1) | A_STANDOUT());}
                    else if cursorstate == 2 {attron(A_UNDERLINE());}
                }
                printw(&format!("{:01X}", buf[z*spalten+screenoffset*spalten+s]&0x0F) ); // Display right nibble
                if z*spalten+screenoffset*spalten+s == cursorpos {
                    if cursorstate == 1 {attroff(COLOR_PAIR(1) | A_STANDOUT());}
                    else if cursorstate == 2 {attroff(A_UNDERLINE());}
                }
                printw(" ");
            } else {
                printw("-- ");
            }
        }
        printw(" "); // Additional space between hex and ascii
        for s in 0 .. spalten {
            if z*spalten+screenoffset*spalten+s == cursorpos {
                if cursorstate == 2 {attron(COLOR_PAIR(1) | A_STANDOUT());}
                else {attron(A_UNDERLINE());}
            }
            if z*spalten+screenoffset*spalten+s < buf.len() {
                if let c @ 32...126 = buf[z*spalten+screenoffset*spalten+s] {
                    if c as char == '%' {
                        printw("%%"); // '%' needs to be escaped by a '%' in ncurses
                    } else {
                        printw(&format!("{}", c as char) );
                    }
                }
                else {printw(&format!(".") );}
            }
            if z*spalten+screenoffset*spalten+s == cursorpos {
                if cursorstate == 2 {attroff(COLOR_PAIR(1) | A_STANDOUT());}
                else {attroff(A_UNDERLINE());}
            }
        }
        printw("\n");
    }
    for _ in 0 .. maxzeilen-zeilen-2 {
        printw("\n"); // Put the cursor on last line of terminal
    }
    if mode == 2 {
        printw(":"); // Indicate that a command can be typed in
    }
    if mode == 3 {
        printw("insert"); // Indicate that insert mode is active
    }
    printw(&format!("{}", command));
}
<commit_msg>Add simple search<commit_after>//! # Hexdino
//!
//! A hex editor with vim like keybindings written in Rust.

#![doc(html_logo_url = "https://raw.githubusercontent.com/Luz/hexdino/master/logo.png")]

use std::io::prelude::*;
use std::fs::OpenOptions;
use std::io::SeekFrom;
use std::path::Path;
use std::error::Error;
use std::env;

extern crate ncurses;
use ncurses::*;

//extern crate getopts;
//use getopts::Options;

fn main() {
    let mut buf = vec![];
    let mut cursorpos:usize = 0;
    let mut cursorstate:usize = 0; //0 is left nibble, 1 is right nibble, 2 is ascii
    let mut screenoffset:usize = 0; // 0 = display data from first line of file
    const SPALTEN:usize = 16;
    let mut command = String::new();

    initscr(); //start ncursesw
    let screenheight : usize = getmaxy(stdscr) as usize;
    cbreak();  //ctrl+z and fg works with this
    noecho();
    start_color();
    init_pair(1, COLOR_GREEN, COLOR_BLACK);

    printw("Welcome to Hexdino.\nPlease find the any-key on your keyboard and press it.\n");

    let args: Vec<_> = env::args().collect();
    /*
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optopt("f", "", "set file name", "NAME");
    opts.optflag("h", "help", "print this help menu");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m }
        Err(f) => { panic!(f.to_string()) }
    };
    if matches.opt_present("h") {
        let brief = format!("Usage: {} FILE [options]", program);
            print!("{}", opts.usage(&brief));
        return;
    }
    let path = if !matches.free.is_empty() {
        matches.free[0].clone()
    } else {
        let brief = format!("Usage: {} FILE [options]", program);
            print!("{}", opts.usage(&brief));
        return;
    };*/

    let path = if args.len() > 1 {
        printw(&format!("Opening file {}", args[1]));
        args[1].clone()
    } else {
        printw(&format!("No file specified. Trying to open foo.txt"));
        "foo.txt".into()
    };

    let path = Path::new(&path);
    let display = path.display();

    getch();
    clear();

    if !has_colors() {
        endwin();
        println!("Your terminal does not support color!\n");
        return;
    }

//TODO in this order: 1)disp files 2) hjkl movement 3) edit file by 'r' 4) save file 5) edit file by 'x, i' 6) search by '/'

    let mut file = match OpenOptions::new().read(true).write(true).create(true).open(&path) {
        Err(why) => panic!("couldn't open {}: {}", display,
            Error::description(&why)),
        Ok(file) => file,
    };

    file.read_to_end(&mut buf).ok().expect("File could not be read.");

    let mut mode = 0; // 0 Command mode, 1 replace next char, 2 type a command, 3 Insert

    draw(&buf, cursorpos, SPALTEN, screenheight, mode, &command, cursorstate, screenoffset);

    let mut key;
//    key = getch() as u8;
//    printw(&format!("{:?}", key));

    let mut quitnow = 0;
    while quitnow == 0 {
        key = getch() as u8;
        if mode == 0 { // movement mode
            match key{
                104 => { // Button "h"
                    if cursorstate == 2 { // ascii mode
                        if cursorpos > 0 { // not at start
                            cursorpos-=1; // go left
                        }
                    }
                    else if cursorstate == 1 { // hex mode, right nibble
                        cursorstate = 0; // left nibble
                    }
                    else if cursorstate == 0 { // hex mode, left nibble
                        if cursorpos > 0 { // not at start
                            cursorstate = 1; // right nibble
                            cursorpos-=1; // go left
                        }
                    }
                },
                106 => { //Button "j"
                    if cursorpos+SPALTEN < buf.len() { // not at end
                        cursorpos+=SPALTEN; // go down
                    }
                    else { // when at end
                        cursorpos=buf.len()-1; // go to end
                    }
                },
                107 => { //Button "k"
                    if cursorpos >= SPALTEN {
                        cursorpos-=SPALTEN;
                    }
                },
                108 => { // Button "l"
                    if cursorstate == 2 { // ascii mode
                        if cursorpos < buf.len()-1 { // not at end
                            cursorpos+=1; // go right
                        }
                    }
                    else if cursorstate == 0 { // hex mode, left nibble
                        cursorstate = 1; // right nibble
                    }
                    else if cursorstate == 1 { // hex mode, right nibble
                        if cursorpos < buf.len()-1 { // not at end
                            cursorstate = 0; // left nibble
                            cursorpos+=1; // go right
                        }
                    }
                },
                 48 => { // Button "0"
                    cursorpos -= cursorpos%SPALTEN; // jump to start of line
                    if cursorstate == 1 { // hex mode, right nibble
                        cursorstate = 0; // left nibble
                    }
                },
                 36 => { // Button "$"
                    if cursorpos-(cursorpos%SPALTEN)+(SPALTEN-1) < buf.len() { // check if no overflow
                        cursorpos = cursorpos-(cursorpos%SPALTEN)+(SPALTEN-1); // jump to end of line
                    }
                    else {
                        cursorpos = buf.len()-1 // jump to end of line
                    }
                    if cursorstate == 0 { // hex mode, left nibble
                        cursorstate = 1; // right nibble
                    }
                },
                114 => { // Button "r"
                    mode = 1; // replaces the next char
                },
                120 => { // Button "x"
                    if buf.len() > 0 { // remove the next char
                        buf.remove(cursorpos);
                    }
                },
                105 => { // Button "i"
                    mode = 3; // go to insert mode
                },
                 58 => { // Button ":"
                     command.clear(); // delete old command
                     mode = 2; // write a command
                 },
                 74 => { // Button "J"
                     if cursorstate == 2 { // ascii mode
                         cursorstate = 0; // hex mode, left nibble
                     }
                     else {
                         cursorstate = 2; // jump to ascii
                     }
                 },
                 63 => { //TODO
                     command.push_str("No helpfile yet");
                 },
                 47 => { //TODO
                     cursorpos = buf.iter().position(|&x| x == 0x33).unwrap();
                 },
                _ => (),
            }

            //Always move screen when cursor leaves screen
            if cursorpos > (screenheight+screenoffset-1)*SPALTEN -1 {
                screenoffset+=1; // move screen
            }
            if cursorpos < screenoffset*SPALTEN {
                screenoffset-=1; // move screen
            }

        } else
        if mode == 1 && cursorstate == 2 { // r was pressed so replace next char in ascii mode
            match key {
                c @ 32...126 => { buf[cursorpos] = c },
                _ => (),
            }
            mode = 0;
        } else
        if mode == 1 {
            let mask = if cursorstate == 0 { 0x0F } else { 0xF0 };
            let shift = if cursorstate == 0 { 4 } else { 0 };
            match key { // Change the selected nibble
                c @ 65... 70 => // A-F
                    { buf[cursorpos] = buf[cursorpos]&mask | (c-55)<<shift },
                c @ 97...102 => // a-f
                    { buf[cursorpos] = buf[cursorpos]&mask | (c-87)<<shift },
                c @ 48... 57 => // 0-9
                    { buf[cursorpos] = buf[cursorpos]&mask | (c-48)<<shift },
                _ => ()
            }
            mode = 0;
        } else
        if mode == 2 {
            match key {
                c @ 32...126 => { command.push(c as char); },
                27 => {command.clear();mode = 0;},
                10 => { // Enter pressed, compute command!
                        if (command == "w".to_string()) || (command == "wq".to_string()) {
                            file.seek(SeekFrom::Start(0)).ok().expect("Filepointer could not be set to 0");
                            file.write_all(&mut buf).ok().expect("File could not be written.");
                            if command == "wq".to_string() {
                                quitnow = 1;
                            }
                            command.clear();mode = 0;
                        }
                        else if command == "q".to_string() {
                            quitnow = 1;
                        }
                        else {
                            command.clear();
                            command.push_str("Bad_command!");
                            mode = 0;
                        }
                    },
                _ => (),
            }
        } else
        if mode == 3 {
            if cursorstate == 0 { // Left nibble
                match key {
                    c @ 65... 70 => // A-F
                        {buf.insert(cursorpos, (c-55)<<4 ); cursorstate = 1;},
                    c @ 97...102 => // a-f
                        {buf.insert(cursorpos, (c-87)<<4 ); cursorstate = 1;},
                    c @ 48... 57 => // 0-9
                        {buf.insert(cursorpos, (c-48)<<4 ); cursorstate = 1;},
                    27 => {mode = 0;},
                    _ => ()
                }
            } else
            if cursorstate == 1 { // Right nibble
                match key {
                    c @ 65...70 => // A-F
                        { buf[cursorpos] = buf[cursorpos]&0xF0 | (c-55); cursorstate = 0; cursorpos+=1; },
                    c @ 97...102 => // a-f
                        { buf[cursorpos] = buf[cursorpos]&0xF0 | (c-87); cursorstate = 0; cursorpos+=1; },
                    c @ 48...57 => // 0-9
                        { buf[cursorpos] = buf[cursorpos]&0xF0 | (c-48); cursorstate = 0; cursorpos+=1; },
                    27 => {mode = 0;},
                    _ => ()
                }
            } else
            if cursorstate == 2 { // Ascii
                match key {
                    c @ 32...126 => { buf.insert(cursorpos, c); cursorpos+=1; },
                    27 => {mode = 0;},
                    _ => (),
                }
            }
        }
        draw(&buf, cursorpos, SPALTEN, screenheight, mode, &command, cursorstate, screenoffset);
    }

    refresh();
    endwin();
}

fn draw(buf:&Vec<u8>, cursorpos:usize, spalten:usize, maxzeilen:usize, mode:usize, command:&String, cursorstate:usize, screenoffset:usize) {
    erase();

    let mut zeilen = buf.len() / spalten;
    if zeilen >= maxzeilen-1 { // Last line reserved for Status/Commands/etc (Like in vim)
        zeilen = maxzeilen-2;
    }

    for z in 0 .. zeilen+1 {
        printw(&format!("{:08X}: ", z*spalten+screenoffset*spalten )); // 8 hex digits (4GB/spalten or 0.25GB@spalten=SPALTEN)
        printw(" "); // Additional space between line number and hex
        for s in 0 .. spalten {
            if z*spalten+screenoffset*spalten+s < buf.len() {
                if z*spalten+screenoffset*spalten+s == cursorpos { // Color of left nibble
                    if cursorstate == 0 {attron(COLOR_PAIR(1) | A_STANDOUT());}
                    else if cursorstate == 2 {attron(A_UNDERLINE());}
                }
                printw(&format!("{:01X}", buf[z*spalten+screenoffset*spalten+s]>>4) ); // Display left nibble
                if z*spalten+screenoffset*spalten+s == cursorpos { // End of color left nibble
                    if cursorstate == 0 {attroff(COLOR_PAIR(1) | A_STANDOUT());}
                    else if cursorstate == 2 {attroff(A_UNDERLINE());}
                }

                if z*spalten+screenoffset*spalten+s == cursorpos { // Color of right nibble
                    if cursorstate == 1 {attron(COLOR_PAIR(1) | A_STANDOUT());}
                    else if cursorstate == 2 {attron(A_UNDERLINE());}
                }
                printw(&format!("{:01X}", buf[z*spalten+screenoffset*spalten+s]&0x0F) ); // Display right nibble
                if z*spalten+screenoffset*spalten+s == cursorpos {
                    if cursorstate == 1 {attroff(COLOR_PAIR(1) | A_STANDOUT());}
                    else if cursorstate == 2 {attroff(A_UNDERLINE());}
                }
                printw(" ");
            } else {
                printw("-- ");
            }
        }
        printw(" "); // Additional space between hex and ascii
        for s in 0 .. spalten {
            if z*spalten+screenoffset*spalten+s == cursorpos {
                if cursorstate == 2 {attron(COLOR_PAIR(1) | A_STANDOUT());}
                else {attron(A_UNDERLINE());}
            }
            if z*spalten+screenoffset*spalten+s < buf.len() {
                if let c @ 32...126 = buf[z*spalten+screenoffset*spalten+s] {
                    if c as char == '%' {
                        printw("%%"); // '%' needs to be escaped by a '%' in ncurses
                    } else {
                        printw(&format!("{}", c as char) );
                    }
                }
                else {printw(&format!(".") );}
            }
            if z*spalten+screenoffset*spalten+s == cursorpos {
                if cursorstate == 2 {attroff(COLOR_PAIR(1) | A_STANDOUT());}
                else {attroff(A_UNDERLINE());}
            }
        }
        printw("\n");
    }
    for _ in 0 .. maxzeilen-zeilen-2 {
        printw("\n"); // Put the cursor on last line of terminal
    }
    if mode == 2 {
        printw(":"); // Indicate that a command can be typed in
    }
    if mode == 3 {
        printw("insert"); // Indicate that insert mode is active
    }
    printw(&format!("{}", command));
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate log;
extern crate env_logger;
extern crate irc;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate hyper;
extern crate hubcaps;
extern crate hyper_native_tls;
extern crate serde_json;

use std::fmt;
use std::thread;
use std::collections::HashMap;
use regex::Regex;

use irc::client::prelude::*;
use irc::client::data::command::Command;

use hyper::Client;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use hubcaps::{Credentials, Github};
use hubcaps::comments::CommentOptions;

fn main() {
    env_logger::init().unwrap();

    let server =
        IrcServer::new("config.json").expect("Couldn't initialize server with config.json");
    server.identify().unwrap();

    let options = server
        .config()
        .options
        .as_ref()
        .expect("No options property within configuration?");

    // FIXME: Add options to ask the bot which channels it's in, and
    // which channels it currently has buffers in.  (Then we can do
    // things like ask the bot to reboot itself, but it will only do so
    // if it's not busy.)
    let mut channel_data: HashMap<String, ChannelData> = HashMap::new();

    for message in server.iter() {
        let message = message.unwrap(); // panic if there's an error

        match message.command {
            Command::PRIVMSG(ref target, ref msg) => {
                match message.source_nickname() {
                    None => {
                        warn!("PRIVMSG without a source! {}",
                              format!("{}", message).trim());
                    }
                    Some(ref source) => {
                        let source_ = String::from(*source);
                        let line = if msg.starts_with("\x01ACTION ") && msg.ends_with("\x01") {
                            ChannelLine {
                                source: source_,
                                is_action: true,
                                message: String::from(&msg[8..msg.len() - 1]),
                            }
                        } else {
                            ChannelLine {
                                source: source_,
                                is_action: false,
                                message: msg.clone(),
                            }
                        };
                        let mynick = server.current_nickname();
                        if target == mynick {
                            // An actual private message.
                            info!("[{}] {}", source, line);
                            handle_bot_command(&server,
                                               options,
                                               None,
                                               &line.message,
                                               source,
                                               false,
                                               None)
                        } else if target.starts_with('#') {
                            // A message in a channel.
                            info!("[{}] {}", target, line);
                            let this_channel_data =
                                channel_data
                                    .entry(target.clone())
                                    .or_insert_with(|| ChannelData::new(&options));
                            match check_command_in_channel(mynick, &line.message) {
                                Some(ref command) => {
                                    handle_bot_command(&server,
                                                       options,
                                                       Some(this_channel_data),
                                                       command,
                                                       target,
                                                       line.is_action,
                                                       Some(source))
                                }
                                None => {
                                    if let Some(response) = this_channel_data.add_line(line) {
                                        server.send_privmsg(target, &*response).unwrap();
                                    }
                                }
                            }
                        } else {
                            warn!("UNEXPECTED TARGET {} in message {}",
                                  target,
                                  format!("{}", message).trim());
                        }
                    }
                }
            }
            Command::INVITE(ref target, ref channel) => {
                if target == server.current_nickname() {
                    // Join channels when invited.
                    server.send_join(channel).unwrap();
                }
            }
            _ => (),
        }
    }
}

// Take a message in the channel, and see if it was a message sent to
// this bot.
fn check_command_in_channel(mynick: &str, msg: &String) -> Option<String> {
    if !msg.starts_with(mynick) {
        return None;
    }
    let after_nick = &msg[mynick.len()..];
    if !after_nick.starts_with(":") && !after_nick.starts_with(",") {
        return None;
    }
    let after_punct = &after_nick[1..];
    Some(String::from(after_punct.trim_left()))
}

fn handle_bot_command(server: &IrcServer,
                      options: &HashMap<String, String>,
                      this_channel_data: Option<&mut ChannelData>,
                      command: &str,
                      response_target: &str,
                      response_is_action: bool,
                      response_username: Option<&str>) {

    let send_line = |response_username: Option<&str>, line: &str| {
        let line_with_nick = match response_username {
            None => String::from(line),
            Some(username) => String::from(username) + ", " + line,
        };
        let adjusted_line = match response_is_action {
            false => line_with_nick,
            true => format!("\x01ACTION {}\x01", line_with_nick),
        };
        server
            .send_privmsg(response_target, &adjusted_line)
            .unwrap();
    };

    if command == "help" {
        send_line(response_username, "The commands I understand are:");
        send_line(None, "  help     Send this message.");
        send_line(None, "  intro    Send a message describing what I do.");
        send_line(None,
                  "  bye      Leave the channel.  (You can /invite me back.)");
        return;
    }

    if command == "intro" {
        let config = server.config();
        send_line(None,
                  "My job is to leave comments in github when the group discusses github issues and takes minutes in IRC.");
        send_line(None,
                  "I separate discussions by the \"Topic:\" lines, and I know what github issues to use only by lines of the form \"Topic github: <url>\", or if the \"Topic:\" line is a github URL.");
        send_line(None,
                  &*format!("I'm only allowed to comment on issues in the repositories: {}.",
                            options["github_repos_allowed"]));
        let owners = if let Some(v) = config.owners.as_ref() {
            v.join(" ")
        } else {
            String::from("")
        };
        send_line(None,
                  &*format!("My source code is at {} and I'm run by {}.",
                            options["source"],
                            owners));
        return;
    }

    if command == "bye" {
        match this_channel_data {
            None => {
                send_line(response_username, "'bye' only works in a channel");
            }
            Some(this_channel_data) => {
                this_channel_data.end_topic();
                server
                    .send(Command::PART(String::from(response_target),
                                        Some(format!("Leaving at request of {}.  Feel free to /invite me back.",
                                                     response_username.unwrap()))))
                    .unwrap();
            }
        }
        return;
    }

    send_line(response_username,
              "Sorry, I don't understand that command.  Try 'help'.");
}

struct ChannelLine {
    source: String,
    is_action: bool,
    message: String,
}

struct TopicData {
    topic: String,
    github_url: Option<String>,
    lines: Vec<ChannelLine>,
    resolutions: Vec<String>,
}

struct ChannelData<'opts> {
    current_topic: Option<TopicData>,
    options: &'opts HashMap<String, String>,
}

impl fmt::Display for ChannelLine {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.is_action {
            write!(f, "* {} {}", self.source, self.message)
        } else {
            write!(f, "<{}> {}", self.source, self.message)
        }
    }
}

impl TopicData {
    fn new(topic: &str) -> TopicData {
        let topic_ = String::from(topic);
        TopicData {
            topic: topic_,
            github_url: None,
            lines: vec![],
            resolutions: vec![],
        }
    }
}

impl fmt::Display for TopicData {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.resolutions.len() == 0 {
            try!(write!(f,
                        "The CSS Working Group just discussed {}.\n\n",
                        self.topic));
        } else {
            try!(write!(f,
                        "The CSS Working Group just discussed {}, and agreed to the following resolutions:\n\n",
                        self.topic));
            for resolution in &self.resolutions {
                try!(write!(f, "```\n{}\n```\n\n", resolution));
            }
        }

        try!(write!(f,
                    "The full IRC log of that discussion\n"));
        try!(write!(f, "\n```\n"));
        for line in &self.lines {
            try!(write!(f, "{}\n", line));
        }
        try!(write!(f, "```\n\n"));
        Ok(())
    }
}

fn ci_starts_with(s: &str, prefix: &str) -> bool {
    assert!(prefix.to_lowercase() == prefix);

    s.len() >= prefix.len() && s[0..prefix.len()].to_lowercase() == prefix
}

fn strip_ci_prefix(s: &str, prefix: &str) -> Option<String> {
    if ci_starts_with(s, prefix) {
        Some(String::from(s[prefix.len()..].trim_left()))
    } else {
        None
    }
}

impl<'opts> ChannelData<'opts> {
    fn new(options_: &'opts HashMap<String, String>) -> ChannelData {
        ChannelData {
            current_topic: None,
            options: options_,
        }
    }

    // Returns the response that should be sent to the message over IRC.
    fn add_line(&mut self, line: ChannelLine) -> Option<String> {
        if let Some(ref topic) = strip_ci_prefix(&line.message, "topic:") {
            self.start_topic(topic);
        }
        if line.source == "trackbot" && line.is_action == true &&
           line.message == "is ending a teleconference." {
            self.end_topic();
        }
        match self.current_topic {
            None => None,
            Some(ref mut data) => {
                let new_url_option = extract_github_url(&line.message, self.options);
                let response = match (new_url_option.as_ref(), data.github_url.as_ref()) {
                    (None, _) => None,
                    // FIXME: Add and implement instructions to cancel!
                    (Some(new_url), None) => {
                        Some(format!("OK, I'll post this discussion to {}", new_url))
                    }
                    (Some(new_url), Some(old_url)) if old_url == new_url => None,
                    (Some(new_url), Some(old_url)) => {
                        Some(format!("OK, I'll post this discussion to {} instead of {} like you said before",
                                     new_url,
                                     old_url))
                    }
                };

                if let Some(new_url) = new_url_option {
                    data.github_url = Some(new_url);
                }

                if line.message.starts_with("RESOLUTION") || line.message.starts_with("RESOLVED") {
                    data.resolutions.push(line.message.clone());
                }

                data.lines.push(line);

                response
            }
        }
    }

    fn start_topic(&mut self, topic: &str) {
        self.end_topic();
        self.current_topic = Some(TopicData::new(topic));
    }

    fn end_topic(&mut self) {
        // TODO: Test the topic boundary code.
        if let Some(topic) = self.current_topic.take() {
            if topic.github_url.is_some() {
                let task = GithubCommentTask::new(topic, self.options);
                task.run();
            }
        }
    }
}

fn extract_github_url(message: &str, options: &HashMap<String, String>) -> Option<String> {
    lazy_static! {
        static ref GITHUB_URL_RE: Regex =
            Regex::new(r"^https://github.com/(?P<repo>[^/]*/[^/]*)/issues/(?P<number>[0-9]+)$")
            .unwrap();
    }
    let ref allowed_repos = options["github_repos_allowed"];
    for prefix in ["topic:", "github topic:"].into_iter() {
        if let Some(ref maybe_url) = strip_ci_prefix(&message, prefix) {
            if let Some(ref caps) = GITHUB_URL_RE.captures(maybe_url) {
                for repo in allowed_repos.split_whitespace() {
                    if caps["repo"] == *repo {
                        return Some(maybe_url.clone());
                    }
                }
            }
        }
    }
    None
}

struct GithubCommentTask {
    data: TopicData,
    github: Github,
}

impl GithubCommentTask {
    fn new(data_: TopicData, options: &HashMap<String, String>) -> GithubCommentTask {
        let github_ =
            Github::new(&*options["github_uastring"],
                        Client::with_connector(HttpsConnector::new(NativeTlsClient::new()
                                                                       .unwrap())),
                        Credentials::Token(options["github_access_token"].clone()));
        GithubCommentTask {
            data: data_,
            github: github_,
        }
    }
    fn run(self) {
        thread::spawn(move || { self.main(); });
    }
    fn main(&self) {
        lazy_static! {
            static ref GITHUB_URL_RE: Regex =
                Regex::new(r"^https://github.com/(?P<owner>[^/]*)/(?P<repo>[^/]*)/issues/(?P<number>[0-9]+)$")
                .unwrap();
        }

        if let Some(ref github_url) = self.data.github_url {
            if let Some(ref caps) = GITHUB_URL_RE.captures(github_url) {
                let repo = self.github
                    .repo(String::from(&caps["owner"]), String::from(&caps["repo"]));
                let issue = repo.issue(caps["number"].parse::<u64>().unwrap());
                let comments = issue.comments();

                let comment_text = format!("{}", self.data);
                comments
                    .create(&CommentOptions { body: comment_text })
                    .unwrap();
            } else {
                warn!("How does {} fail to match now when it matched before?",
                      github_url)
            }
        }
    }
}
<commit_msg>A few more comments.<commit_after>#[macro_use]
extern crate log;
extern crate env_logger;
extern crate irc;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate hyper;
extern crate hubcaps;
extern crate hyper_native_tls;
extern crate serde_json;

use std::fmt;
use std::thread;
use std::collections::HashMap;
use regex::Regex;

use irc::client::prelude::*;
use irc::client::data::command::Command;

use hyper::Client;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use hubcaps::{Credentials, Github};
use hubcaps::comments::CommentOptions;

fn main() {
    env_logger::init().unwrap();

    let server =
        IrcServer::new("config.json").expect("Couldn't initialize server with config.json");
    server.identify().unwrap();

    let options = server
        .config()
        .options
        .as_ref()
        .expect("No options property within configuration?");

    // FIXME: Add options to ask the bot which channels it's in, and
    // which channels it currently has buffers in.  (Then we can do
    // things like ask the bot to reboot itself, but it will only do so
    // if it's not busy.)
    let mut channel_data: HashMap<String, ChannelData> = HashMap::new();

    for message in server.iter() {
        let message = message.unwrap(); // panic if there's an error

        match message.command {
            Command::PRIVMSG(ref target, ref msg) => {
                match message.source_nickname() {
                    None => {
                        warn!("PRIVMSG without a source! {}",
                              format!("{}", message).trim());
                    }
                    Some(ref source) => {
                        let source_ = String::from(*source);
                        let line = if msg.starts_with("\x01ACTION ") && msg.ends_with("\x01") {
                            ChannelLine {
                                source: source_,
                                is_action: true,
                                message: String::from(&msg[8..msg.len() - 1]),
                            }
                        } else {
                            ChannelLine {
                                source: source_,
                                is_action: false,
                                message: msg.clone(),
                            }
                        };
                        let mynick = server.current_nickname();
                        if target == mynick {
                            // An actual private message.
                            info!("[{}] {}", source, line);
                            handle_bot_command(&server,
                                               options,
                                               None,
                                               &line.message,
                                               source,
                                               false,
                                               None)
                        } else if target.starts_with('#') {
                            // A message in a channel.
                            info!("[{}] {}", target, line);
                            let this_channel_data =
                                channel_data
                                    .entry(target.clone())
                                    .or_insert_with(|| ChannelData::new(&options));
                            match check_command_in_channel(mynick, &line.message) {
                                Some(ref command) => {
                                    handle_bot_command(&server,
                                                       options,
                                                       Some(this_channel_data),
                                                       command,
                                                       target,
                                                       line.is_action,
                                                       Some(source))
                                }
                                None => {
                                    if let Some(response) = this_channel_data.add_line(line) {
                                        server.send_privmsg(target, &*response).unwrap();
                                    }
                                }
                            }
                        } else {
                            warn!("UNEXPECTED TARGET {} in message {}",
                                  target,
                                  format!("{}", message).trim());
                        }
                    }
                }
            }
            Command::INVITE(ref target, ref channel) => {
                if target == server.current_nickname() {
                    // Join channels when invited.
                    server.send_join(channel).unwrap();
                }
            }
            _ => (),
        }
    }
}

// Take a message in the channel, and see if it was a message sent to
// this bot.
fn check_command_in_channel(mynick: &str, msg: &String) -> Option<String> {
    if !msg.starts_with(mynick) {
        return None;
    }
    let after_nick = &msg[mynick.len()..];
    if !after_nick.starts_with(":") && !after_nick.starts_with(",") {
        return None;
    }
    let after_punct = &after_nick[1..];
    Some(String::from(after_punct.trim_left()))
}

fn handle_bot_command(server: &IrcServer,
                      options: &HashMap<String, String>,
                      this_channel_data: Option<&mut ChannelData>,
                      command: &str,
                      response_target: &str,
                      response_is_action: bool,
                      response_username: Option<&str>) {

    let send_line = |response_username: Option<&str>, line: &str| {
        let line_with_nick = match response_username {
            None => String::from(line),
            Some(username) => String::from(username) + ", " + line,
        };
        let adjusted_line = match response_is_action {
            false => line_with_nick,
            true => format!("\x01ACTION {}\x01", line_with_nick),
        };
        server
            .send_privmsg(response_target, &adjusted_line)
            .unwrap();
    };

    if command == "help" {
        send_line(response_username, "The commands I understand are:");
        send_line(None, "  help     Send this message.");
        send_line(None, "  intro    Send a message describing what I do.");
        send_line(None,
                  "  bye      Leave the channel.  (You can /invite me back.)");
        return;
    }

    if command == "intro" {
        let config = server.config();
        send_line(None,
                  "My job is to leave comments in github when the group discusses github issues and takes minutes in IRC.");
        send_line(None,
                  "I separate discussions by the \"Topic:\" lines, and I know what github issues to use only by lines of the form \"Topic github: <url>\", or if the \"Topic:\" line is a github URL.");
        send_line(None,
                  &*format!("I'm only allowed to comment on issues in the repositories: {}.",
                            options["github_repos_allowed"]));
        let owners = if let Some(v) = config.owners.as_ref() {
            v.join(" ")
        } else {
            String::from("")
        };
        send_line(None,
                  &*format!("My source code is at {} and I'm run by {}.",
                            options["source"],
                            owners));
        return;
    }

    if command == "bye" {
        match this_channel_data {
            None => {
                send_line(response_username, "'bye' only works in a channel");
            }
            Some(this_channel_data) => {
                this_channel_data.end_topic();
                server
                    .send(Command::PART(String::from(response_target),
                                        Some(format!("Leaving at request of {}.  Feel free to /invite me back.",
                                                     response_username.unwrap()))))
                    .unwrap();
            }
        }
        return;
    }

    send_line(response_username,
              "Sorry, I don't understand that command.  Try 'help'.");
}

struct ChannelLine {
    source: String,
    is_action: bool,
    message: String,
}

struct TopicData {
    topic: String,
    github_url: Option<String>,
    lines: Vec<ChannelLine>,
    resolutions: Vec<String>,
}

struct ChannelData<'opts> {
    current_topic: Option<TopicData>,
    options: &'opts HashMap<String, String>,
}

impl fmt::Display for ChannelLine {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.is_action {
            write!(f, "* {} {}", self.source, self.message)
        } else {
            write!(f, "<{}> {}", self.source, self.message)
        }
    }
}

impl TopicData {
    fn new(topic: &str) -> TopicData {
        let topic_ = String::from(topic);
        TopicData {
            topic: topic_,
            github_url: None,
            lines: vec![],
            resolutions: vec![],
        }
    }
}

impl fmt::Display for TopicData {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.resolutions.len() == 0 {
            try!(write!(f,
                        "The CSS Working Group just discussed {}.\n\n",
                        self.topic));
        } else {
            try!(write!(f,
                        "The CSS Working Group just discussed {}, and agreed to the following resolutions:\n\n",
                        self.topic));
            for resolution in &self.resolutions {
                try!(write!(f, "```\n{}\n```\n\n", resolution));
            }
        }

        try!(write!(f,
                    "The full IRC log of that discussion\n"));
        try!(write!(f, "\n```\n"));
        for line in &self.lines {
            try!(write!(f, "{}\n", line));
        }
        try!(write!(f, "```\n\n"));
        Ok(())
    }
}

/// A case-insensitive version of starts_with.
fn ci_starts_with(s: &str, prefix: &str) -> bool {
    assert!(prefix.to_lowercase() == prefix);

    s.len() >= prefix.len() && s[0..prefix.len()].to_lowercase() == prefix
}

/// Remove a case-insensitive start of the line, and if that prefix is
/// present return the rest of the line.
fn strip_ci_prefix(s: &str, prefix: &str) -> Option<String> {
    if ci_starts_with(s, prefix) {
        Some(String::from(s[prefix.len()..].trim_left()))
    } else {
        None
    }
}

impl<'opts> ChannelData<'opts> {
    fn new(options_: &'opts HashMap<String, String>) -> ChannelData {
        ChannelData {
            current_topic: None,
            options: options_,
        }
    }

    // Returns the response that should be sent to the message over IRC.
    fn add_line(&mut self, line: ChannelLine) -> Option<String> {
        if let Some(ref topic) = strip_ci_prefix(&line.message, "topic:") {
            self.start_topic(topic);
        }
        if line.source == "trackbot" && line.is_action == true &&
           line.message == "is ending a teleconference." {
            self.end_topic();
        }
        match self.current_topic {
            None => None,
            Some(ref mut data) => {
                let new_url_option = extract_github_url(&line.message, self.options);
                let response = match (new_url_option.as_ref(), data.github_url.as_ref()) {
                    (None, _) => None,
                    // FIXME: Add and implement instructions to cancel!
                    (Some(new_url), None) => {
                        Some(format!("OK, I'll post this discussion to {}", new_url))
                    }
                    (Some(new_url), Some(old_url)) if old_url == new_url => None,
                    (Some(new_url), Some(old_url)) => {
                        Some(format!("OK, I'll post this discussion to {} instead of {} like you said before",
                                     new_url,
                                     old_url))
                    }
                };

                if let Some(new_url) = new_url_option {
                    data.github_url = Some(new_url);
                }

                if line.message.starts_with("RESOLUTION") || line.message.starts_with("RESOLVED") {
                    data.resolutions.push(line.message.clone());
                }

                data.lines.push(line);

                response
            }
        }
    }

    fn start_topic(&mut self, topic: &str) {
        self.end_topic();
        self.current_topic = Some(TopicData::new(topic));
    }

    fn end_topic(&mut self) {
        // TODO: Test the topic boundary code.
        if let Some(topic) = self.current_topic.take() {
            if topic.github_url.is_some() {
                let task = GithubCommentTask::new(topic, self.options);
                task.run();
            }
        }
    }
}

fn extract_github_url(message: &str, options: &HashMap<String, String>) -> Option<String> {
    lazy_static! {
        static ref GITHUB_URL_RE: Regex =
            Regex::new(r"^https://github.com/(?P<repo>[^/]*/[^/]*)/issues/(?P<number>[0-9]+)$")
            .unwrap();
    }
    let ref allowed_repos = options["github_repos_allowed"];
    for prefix in ["topic:", "github topic:"].into_iter() {
        if let Some(ref maybe_url) = strip_ci_prefix(&message, prefix) {
            if let Some(ref caps) = GITHUB_URL_RE.captures(maybe_url) {
                for repo in allowed_repos.split_whitespace() {
                    if caps["repo"] == *repo {
                        return Some(maybe_url.clone());
                    }
                }
            }
        }
    }
    None
}

struct GithubCommentTask {
    data: TopicData,
    github: Github,
}

impl GithubCommentTask {
    fn new(data_: TopicData, options: &HashMap<String, String>) -> GithubCommentTask {
        let github_ =
            Github::new(&*options["github_uastring"],
                        Client::with_connector(HttpsConnector::new(NativeTlsClient::new()
                                                                       .unwrap())),
                        Credentials::Token(options["github_access_token"].clone()));
        GithubCommentTask {
            data: data_,
            github: github_,
        }
    }
    fn run(self) {
        thread::spawn(move || { self.main(); });
    }
    fn main(&self) {
        lazy_static! {
            static ref GITHUB_URL_RE: Regex =
                Regex::new(r"^https://github.com/(?P<owner>[^/]*)/(?P<repo>[^/]*)/issues/(?P<number>[0-9]+)$")
                .unwrap();
        }

        if let Some(ref github_url) = self.data.github_url {
            if let Some(ref caps) = GITHUB_URL_RE.captures(github_url) {
                let repo = self.github
                    .repo(String::from(&caps["owner"]), String::from(&caps["repo"]));
                let issue = repo.issue(caps["number"].parse::<u64>().unwrap());
                let comments = issue.comments();

                let comment_text = format!("{}", self.data);
                comments
                    .create(&CommentOptions { body: comment_text })
                    .unwrap();
            } else {
                warn!("How does {} fail to match now when it matched before?",
                      github_url)
            }
        }
    }
}
<|endoftext|>


<commit_before>extern crate ghoti;
extern crate getopts;

use getopts::Options;

use std::env;
use std::fs::File;
use std::path::Path;

fn print_usage(opts: Options) {
    let usage = "Usage: ghoti [options] <filename>";
    print!("{}", opts.usage(usage));
}

fn main() {
    // Set up CLI options
    let mut opts = Options::new();
    opts.optflag("h", "help", "print this help menu");
    opts.optflag("v", "version", "print version info");

    // Ignore the first element of env::args() (program name)
    let args: Vec<String> = env::args().skip(1).collect();
    let matches = opts.parse(&args).unwrap();

    // Iterate through CLI flags
    if matches.opt_present("version") {
        println!("ghoti {}", env!("CARGO_PKG_VERSION"));
        return;
    } else if matches.opt_present("help") || matches.free.is_empty() {
        print_usage(opts);
        return;
    }

    let filename = matches.free[0].clone();

    match File::open(Path::new(&filename)) {
        Ok(_) => println!("Hello, {}!", filename),
        Err(_) => panic!("Couldn't open {}.", filename),
    };
}
<commit_msg>Check spelling of a word from program CLI<commit_after>extern crate ghoti;
extern crate getopts;

use getopts::Options;
use ghoti::correction;
use std::env;

fn print_usage(opts: Options) {
    let usage = "Usage: ghoti [options] <word>";
    print!("{}", opts.usage(usage));
}

fn main() {
    // Set up CLI options
    let mut opts = Options::new();
    opts.optflag("h", "help", "print this help menu");
    opts.optflag("v", "version", "print version info");

    // Ignore the first element of env::args() (program name)
    let args: Vec<String> = env::args().skip(1).collect();
    let matches = opts.parse(&args).unwrap();

    // Iterate through CLI flags
    if matches.opt_present("version") {
        println!("ghoti {}", env!("CARGO_PKG_VERSION"));
        return;
    } else if matches.opt_present("help") || matches.free.is_empty() {
        print_usage(opts);
        return;
    }

    println!("{}", correction(&matches.free[0]));
}
<|endoftext|>


<commit_before>
extern crate chrono;
extern crate docopt;

use std::process::Command;

use std::io::prelude::*;
use std::net::TcpStream;

use docopt::Docopt;

use std::str::FromStr;

extern crate libc;
use libc::*;
use std::ffi::CString;
use std::default::Default;

// #define MFSTYPENAMELEN  16  length of fs type name including null
// #define MAXPATHLEN      1024
// #define MNAMELEN        MAXPATHLEN
   // struct statfs { /* when _DARWIN_FEATURE_64_BIT_INODE is defined */
   //       uint32_t    f_bsize;        /* fundamental file system block size */
   //       int32_t     f_iosize;       /* optimal transfer block size */
   //       uint64_t    f_blocks;       /* total data blocks in file system */
   //       uint64_t    f_bfree;        /* free blocks in fs */
   //       uint64_t    f_bavail;       /* free blocks avail to non-superuser */
   //       uint64_t    f_files;        /* total file nodes in file system */
   //       uint64_t    f_ffree;        /* free file nodes in fs */
   //       fsid_t      f_fsid;         /* file system id */
   //       uid_t       f_owner;        /* user that mounted the filesystem */
   //       uint32_t    f_type;         /* type of filesystem */
   //       uint32_t    f_flags;        /* copy of mount exported flags */
   //       uint32_t    f_fssubtype;    /* fs sub-type (flavor) */
   //       char        f_fstypename[MFSTYPENAMELEN];   /* fs type name */
   //       char        f_mntonname[MAXPATHLEN];        /* directory on which mounted */
   //       char        f_mntfromname[MAXPATHLEN];      /* mounted filesystem */
   //       uint32_t    f_reserved[8];  /* For future use */
   //   };

type Uid = uint32_t;

#[repr(C)]
struct StatFs {
	f_bsize: uint32_t,
	f_iosize: int32_t,
	f_blocks: uint64_t,
	f_bfree:  uint64_t,
	f_bavail: uint64_t,
	f_files:  uint64_t,
	f_ffree:  uint64_t,
	f_fsid:	  [uint32_t; 2],
	f_owner:  Uid,
	f_type:	  uint32_t,
	f_flags:  uint32_t,
	f_fstypename: 	[c_char; 16],
	f_mntonname: 	[c_char; 1024],
	f_mntfromname: 	[c_char; 1024],
	f_reserved: 	[uint32_t; 8],
}

impl Default for StatFs {
	fn default() -> StatFs {
		StatFs{
			f_bsize: 0,
			f_iosize: 0,
			f_blocks: 0,
			f_bfree: 0,
			f_bavail: 0,
			f_files: 0,
			f_ffree: 0,
			f_fsid: [0; 2],
			f_owner: 0,
			f_type: 0,
			f_flags: 0,
			f_fstypename: [0; 16],
			f_mntonname:  [0; 1024],
			f_mntfromname:[0; 1024],
			f_reserved: [0; 8]
		}
	}
}

extern {
	fn statfs(path: *const c_char, stafs: *mut StatFs) -> size_t;
	// statfs(const char *path, struct statfs *buf);
}

// Write the Docopt usage string. dfrites ?
static USAGE: &'static str = "
Usage: durite -g GHOSTNAME -l HOSTNAME [-p PORT]
       durite (--help | -h)

Options:
    -h, --help     Show this screen.
    -l HOSTNAME    Hostname to advertise in graphite.
    -g GHOSTNAME   Graphite Hostname.
    -p PORT        Graphite port [default: 2003]
";

#[cfg(not(any(target_os="macos")))]
fn disk_free() -> std::process::Output {
	Command::new("df")
		.arg("--portability").output().unwrap_or_else(|e| { panic!("failed to execute process: {}", e) })
}

#[cfg(any(target_os="macos"))]
fn disk_free() -> std::process::Output {
	Command::new("df")
		.arg("-k").output().unwrap_or_else(|e| { panic!("failed to execute process: {}", e) })
}

fn send_content(stream: &mut std::net::TcpStream, hostname: &str, probe_and_value: String, timestamp: i64) {
	let content = format!("durite.{}.{} {}\n", hostname, probe_and_value, timestamp);
    println!("{}", content);
	let _ = stream.write(&content.as_bytes());
}

fn main() {

	// Parse argv and exit the program with an error message if it fails.
	let args = Docopt::new(USAGE)
		.and_then(|d| d.argv(std::env::args().into_iter()).parse())
		.unwrap_or_else(|e| e.exit());

    let connect_string = format!("{}:{}", args.get_str("-g"), args.get_str("-p"));
    println!("Connect string is {}", connect_string);
    let my_hostname = args.get_str("-l");

	while true {
	    let dt = chrono::UTC::now();
	    let timestamp = dt.timestamp();
	    let o = disk_free();
		let stdout = String::from_utf8(o.stdout).
			ok().
			unwrap();

		let lines = stdout.split("\n");

	    match TcpStream::connect(&*connect_string) {
	    	Ok(s) => {
	    		let mut stream = s;
				for line in lines {
					if line.starts_with("/") {
						let values: Vec<&str> = line.split(" ").filter(|e| {
							e.len() != 0
						}).collect();

						let disk = values[0];
						// du reports kbytes
                        let all: u64 = u64::from_str(values[1]).unwrap() / 1024;
						let available: u64 = u64::from_str(values[3]).unwrap() / 1024;
						// let w = values[8].to_string();
						// let mut st: StatFs = Default::default();
						// let mp = CString::new(w.into_bytes()).unwrap();
						// unsafe {
						// 	let o = statfs(mp.as_ptr(), &mut st);
						// 	println!("o{} {:?}", o, st.f_bfree);
						// }
						send_content(&mut stream, my_hostname, format!("{}.available {:3.3}", disk, available), timestamp);
						send_content(&mut stream, my_hostname, format!("{}.all {}", disk, all), timestamp);
				 	}
				 }
	    	},
	    	Err(e) => println!("Unable to connect to {}: {:?}\nIgnoring this data point.", connect_string, e)
		}
	 	std::thread::sleep_ms(10000);
	}
}
<commit_msg>remove debug<commit_after>
extern crate chrono;
extern crate docopt;

use std::process::Command;

use std::io::prelude::*;
use std::net::TcpStream;

use docopt::Docopt;

use std::str::FromStr;

extern crate libc;
use libc::*;
use std::ffi::CString;
use std::default::Default;

// #define MFSTYPENAMELEN  16  length of fs type name including null
// #define MAXPATHLEN      1024
// #define MNAMELEN        MAXPATHLEN
   // struct statfs { /* when _DARWIN_FEATURE_64_BIT_INODE is defined */
   //       uint32_t    f_bsize;        /* fundamental file system block size */
   //       int32_t     f_iosize;       /* optimal transfer block size */
   //       uint64_t    f_blocks;       /* total data blocks in file system */
   //       uint64_t    f_bfree;        /* free blocks in fs */
   //       uint64_t    f_bavail;       /* free blocks avail to non-superuser */
   //       uint64_t    f_files;        /* total file nodes in file system */
   //       uint64_t    f_ffree;        /* free file nodes in fs */
   //       fsid_t      f_fsid;         /* file system id */
   //       uid_t       f_owner;        /* user that mounted the filesystem */
   //       uint32_t    f_type;         /* type of filesystem */
   //       uint32_t    f_flags;        /* copy of mount exported flags */
   //       uint32_t    f_fssubtype;    /* fs sub-type (flavor) */
   //       char        f_fstypename[MFSTYPENAMELEN];   /* fs type name */
   //       char        f_mntonname[MAXPATHLEN];        /* directory on which mounted */
   //       char        f_mntfromname[MAXPATHLEN];      /* mounted filesystem */
   //       uint32_t    f_reserved[8];  /* For future use */
   //   };

type Uid = uint32_t;

#[repr(C)]
struct StatFs {
	f_bsize: uint32_t,
	f_iosize: int32_t,
	f_blocks: uint64_t,
	f_bfree:  uint64_t,
	f_bavail: uint64_t,
	f_files:  uint64_t,
	f_ffree:  uint64_t,
	f_fsid:	  [uint32_t; 2],
	f_owner:  Uid,
	f_type:	  uint32_t,
	f_flags:  uint32_t,
	f_fstypename: 	[c_char; 16],
	f_mntonname: 	[c_char; 1024],
	f_mntfromname: 	[c_char; 1024],
	f_reserved: 	[uint32_t; 8],
}

impl Default for StatFs {
	fn default() -> StatFs {
		StatFs{
			f_bsize: 0,
			f_iosize: 0,
			f_blocks: 0,
			f_bfree: 0,
			f_bavail: 0,
			f_files: 0,
			f_ffree: 0,
			f_fsid: [0; 2],
			f_owner: 0,
			f_type: 0,
			f_flags: 0,
			f_fstypename: [0; 16],
			f_mntonname:  [0; 1024],
			f_mntfromname:[0; 1024],
			f_reserved: [0; 8]
		}
	}
}

extern {
	fn statfs(path: *const c_char, stafs: *mut StatFs) -> size_t;
	// statfs(const char *path, struct statfs *buf);
}

// Write the Docopt usage string. dfrites ?
static USAGE: &'static str = "
Usage: durite -g GHOSTNAME -l HOSTNAME [-p PORT]
       durite (--help | -h)

Options:
    -h, --help     Show this screen.
    -l HOSTNAME    Hostname to advertise in graphite.
    -g GHOSTNAME   Graphite Hostname.
    -p PORT        Graphite port [default: 2003]
";

#[cfg(not(any(target_os="macos")))]
fn disk_free() -> std::process::Output {
	Command::new("df")
		.arg("--portability").output().unwrap_or_else(|e| { panic!("failed to execute process: {}", e) })
}

#[cfg(any(target_os="macos"))]
fn disk_free() -> std::process::Output {
	Command::new("df")
		.arg("-k").output().unwrap_or_else(|e| { panic!("failed to execute process: {}", e) })
}

fn send_content(stream: &mut std::net::TcpStream, hostname: &str, probe_and_value: String, timestamp: i64) {
	let content = format!("durite.{}.{} {}\n", hostname, probe_and_value, timestamp);
	let _ = stream.write(&content.as_bytes());
}

fn main() {

	// Parse argv and exit the program with an error message if it fails.
	let args = Docopt::new(USAGE)
		.and_then(|d| d.argv(std::env::args().into_iter()).parse())
		.unwrap_or_else(|e| e.exit());

    let connect_string = format!("{}:{}", args.get_str("-g"), args.get_str("-p"));
    println!("Connect string is {}", connect_string);
    let my_hostname = args.get_str("-l");

	while true {
	    let dt = chrono::UTC::now();
	    let timestamp = dt.timestamp();
	    let o = disk_free();
		let stdout = String::from_utf8(o.stdout).
			ok().
			unwrap();

		let lines = stdout.split("\n");

	    match TcpStream::connect(&*connect_string) {
	    	Ok(s) => {
	    		let mut stream = s;
				for line in lines {
					if line.starts_with("/") {
						let values: Vec<&str> = line.split(" ").filter(|e| {
							e.len() != 0
						}).collect();

						let disk = values[0];
						// du reports kbytes
                        let all: u64 = u64::from_str(values[1]).unwrap() / 1024;
						let available: u64 = u64::from_str(values[3]).unwrap() / 1024;
						// let w = values[8].to_string();
						// let mut st: StatFs = Default::default();
						// let mp = CString::new(w.into_bytes()).unwrap();
						// unsafe {
						// 	let o = statfs(mp.as_ptr(), &mut st);
						// 	println!("o{} {:?}", o, st.f_bfree);
						// }

                        // {:6E} 6 digit, lower exponential format
						send_content(&mut stream, my_hostname, format!("{}.available {:3.3}", disk, available), timestamp);
						send_content(&mut stream, my_hostname, format!("{}.all {}", disk, all), timestamp);
				 	}
				 }
	    	},
	    	Err(e) => println!("Unable to connect to {}: {:?}\nIgnoring this data point.", connect_string, e)
		}
	 	std::thread::sleep_ms(10000);
	}
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate glium;
extern crate cgmath;
extern crate noise;
extern crate rand;

mod chunk;
mod position;
mod scheduler;

use std::time::{Instant, Duration, UNIX_EPOCH, SystemTime};
use std::env;

use glium::{Surface, glutin, DisplayBuild, Program};
use glium::glutin::{ElementState, Event, VirtualKeyCode};
use cgmath::{Matrix4, Point3, Vector3, Deg};

use scheduler::MasterScheduler;

fn main() {
    let display = glutin::WindowBuilder::new()
        .with_vsync()
        .with_depth_buffer(24)
        .with_dimensions(1024, 768)
        .with_title(format!("Parallel Voxels"))
        .build_glium()
        .unwrap();

    let program = Program::from_source(&display, "
    #version 140
    uniform mat4 model_view_projection;
    uniform vec2 chunk_position;
    in vec3 position;
    in vec4 color;
    in float occlusion;
    out vec4 v_color;
    void main() {
        gl_Position = model_view_projection * (vec4(chunk_position*16.0, 0.0, 0.0) + vec4(position, 1.0));
        v_color = color / 255.0 * occlusion;
    }
    ", "
    #version 140
    in vec4 v_color;
    out vec4 f_color;
    void main() {
        f_color = vec4(v_color.rgb, 1.0);
    }
    ", None).unwrap();

    let mut move_camera = true;
    let mut distant_camera = false;
    let mut camera_side_speed = 0.0;
    let mut camera_speed = 0.1;

    let mut testing_mode = 0;
    let mut camera_position = [0.5, 0.0, 0.0f32];
    let mut seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as u32;
    let mut thread_count = 4;

    for arg in env::args() {
        let arg = arg.split('=').collect::<Vec<&str>>();
        match arg[0] {
            "-s" => {
                seed = arg[1].parse().unwrap();
            },
            "-tc" => {
                thread_count = arg[1].parse().unwrap();
            },
            "-tm" => {
                testing_mode = 1;
                camera_speed = 16.0;
                distant_camera = true;
            }
            _ => {

            }
        }
    }

    let mut scheduler = MasterScheduler::new(seed, thread_count);

    let timestep: Duration = Duration::new(0, 16666667);
    let mut time = Duration::new(0, 0);
    let mut prev_time = Instant::now();

    'outer: loop {
        let now = Instant::now();
        time = now - prev_time + time;
        prev_time = now;

        while time > timestep {
            time = time - timestep;

            scheduler.update(camera_position, &display);

            if move_camera {
                camera_position[0] += camera_side_speed;
                camera_position[1] += camera_speed;
            }

            if !distant_camera {
                camera_position[2] = {
                    let pos = (camera_position[0].floor() as i32, camera_position[1].floor() as i32 & !3);
                    let height = {
                        *[scheduler.get_height([pos.0, pos.1 - 4]),
                        scheduler.get_height([pos.0, pos.1 - 3]),
                        scheduler.get_height([pos.0, pos.1 - 2]),
                        scheduler.get_height([pos.0, pos.1 - 1]),
                        scheduler.get_height([pos.0, pos.1]),
                        scheduler.get_height([pos.0, pos.1 + 1]),
                        scheduler.get_height([pos.0, pos.1 + 2]),
                        scheduler.get_height([pos.0, pos.1 + 3])].iter().max().unwrap() + 4
                    };
                    let next_height = {
                        *[scheduler.get_height([pos.0, pos.1]),
                        scheduler.get_height([pos.0, pos.1 + 1]),
                        scheduler.get_height([pos.0, pos.1 + 2]),
                        scheduler.get_height([pos.0, pos.1 + 3]),
                        scheduler.get_height([pos.0, pos.1 + 4]),
                        scheduler.get_height([pos.0, pos.1 + 5]),
                        scheduler.get_height([pos.0, pos.1 + 6]),
                        scheduler.get_height([pos.0, pos.1 + 7])].iter().max().unwrap() + 4
                    };

                    let y_interp = (camera_position[1] - pos.1 as f32)/4.0;

                    ((height as f32) * (1.0 - y_interp) + (next_height as f32) * y_interp).max(camera_position[2]-0.05)
                };
            } else {
                camera_position[2] = 128.0;
            }

            if testing_mode != 0 && camera_position[1] >= 4096.0 {
                match testing_mode {
                    1 => {
                        camera_position[1] = 0.0;
                        testing_mode = 2;
                        scheduler.serially_generate = !scheduler.serially_generate;
                        scheduler.serially_populate = !scheduler.serially_populate;
                        scheduler.serially_mesh = !scheduler.serially_mesh;
                    },
                    2 => {
                        break 'outer;
                    },
                    _ => {
                        unreachable!();
                    }
                }
            }
        }

        let (width, height) = {
            let window = display.get_window().unwrap();
            let size = window.get_inner_size_points().unwrap();
            (size.0 as f32, size.1 as f32)
        };

        let projection = cgmath::perspective(Deg {s: 90.0}, width / height, 0.01, 1000.0f32);

        let model_view = if distant_camera {
            Matrix4::look_at(Point3::from(camera_position), Point3::from(camera_position) + Vector3::new(0.0, 0.0, -1.0f32), Vector3::new(0.0, 1.0, 0.0f32))
        } else {
            Matrix4::look_at(Point3::from(camera_position), Point3::from(camera_position) + Vector3::new(0.0, 1.0, 0.0f32), Vector3::new(0.0, 0.0, 1.0f32))
        };

        let model_view_projection = projection * model_view;

        let mut frame = display.draw();
        frame.clear_color_and_depth((0.0, 0.2, 0.8, 0.0), 1.0);

        scheduler.render(&mut frame, &program, model_view_projection);

        frame.finish().unwrap();

        for event in display.poll_events() {
            match event {
                Event::Closed => return,
                Event::KeyboardInput(state, _, key_code) => {
                    if state == ElementState::Released {
                        if testing_mode != 0 {
                            continue;
                        }
                        if let Some(key_code) = key_code {
                            match key_code {
                                VirtualKeyCode::Key1 => {
                                    println!("Serial generation enabled");
                                    scheduler.serially_generate = true;
                                },
                                VirtualKeyCode::Key2 => {
                                    println!("Parallel generation enabled");
                                    scheduler.serially_generate = false;
                                },
                                VirtualKeyCode::Key3 => {
                                    println!("Serial population enabled");
                                    scheduler.serially_populate = true;
                                },
                                VirtualKeyCode::Key4 => {
                                    println!("Parallel population enabled");
                                    scheduler.serially_populate = false;
                                },
                                VirtualKeyCode::Key5 => {
                                    println!("Serial meshing enabled");
                                    scheduler.serially_mesh = true;
                                },
                                VirtualKeyCode::Key6 => {
                                    println!("Parallel meshing enabled");
                                    scheduler.serially_mesh = false;
                                },
                                VirtualKeyCode::Space => {
                                    move_camera = !move_camera;
                                },
                                VirtualKeyCode::P => {
                                    distant_camera = !distant_camera;
                                    camera_position[2] = 0.0;
                                },
                                VirtualKeyCode::Minus => {
                                    camera_speed -= 0.1;
                                },
                                VirtualKeyCode::Equals => {
                                    camera_speed += 0.1;
                                },
                                VirtualKeyCode::LBracket => {
                                    camera_side_speed -= 0.1;
                                },
                                VirtualKeyCode::RBracket => {
                                    camera_side_speed += 0.1;
                                },
                                VirtualKeyCode::Semicolon => {
                                    camera_side_speed = 0.0;
                                    camera_speed = 0.0;
                                },
                                VirtualKeyCode::Escape => {
                                    break 'outer;
                                },
                                _ => ()
                            }
                        }
                    }
                },
                _ => ()
            }
        }
    }

    println!("SG: {:.2} ms PG: {:.2} ms", scheduler.serial_generator_time, scheduler.parallel_generator_time);
    println!("SP: {:.2} ms PP: {:.2} ms", scheduler.serial_populator_time, scheduler.parallel_populator_time);
    println!("SM: {:.2} ms PM: {:.2} ms", scheduler.serial_meshing_time, scheduler.parallel_meshing_time);
}
<commit_msg>Remove one second from time accumulator when possible to prevent accumulator from growing unboundedly<commit_after>#[macro_use]
extern crate glium;
extern crate cgmath;
extern crate noise;
extern crate rand;

mod chunk;
mod position;
mod scheduler;

use std::time::{Instant, Duration, UNIX_EPOCH, SystemTime};
use std::env;

use glium::{Surface, glutin, DisplayBuild, Program};
use glium::glutin::{ElementState, Event, VirtualKeyCode};
use cgmath::{Matrix4, Point3, Vector3, Deg};

use scheduler::MasterScheduler;

fn main() {
    let display = glutin::WindowBuilder::new()
        .with_vsync()
        .with_depth_buffer(24)
        .with_dimensions(1024, 768)
        .with_title(format!("Parallel Voxels"))
        .build_glium()
        .unwrap();

    let program = Program::from_source(&display, "
    #version 140
    uniform mat4 model_view_projection;
    uniform vec2 chunk_position;
    in vec3 position;
    in vec4 color;
    in float occlusion;
    out vec4 v_color;
    void main() {
        gl_Position = model_view_projection * (vec4(chunk_position*16.0, 0.0, 0.0) + vec4(position, 1.0));
        v_color = color / 255.0 * occlusion;
    }
    ", "
    #version 140
    in vec4 v_color;
    out vec4 f_color;
    void main() {
        f_color = vec4(v_color.rgb, 1.0);
    }
    ", None).unwrap();

    let mut move_camera = true;
    let mut distant_camera = false;
    let mut camera_side_speed = 0.0;
    let mut camera_speed = 0.1;

    let mut testing_mode = 0;
    let mut camera_position = [0.5, 0.0, 0.0f32];
    let mut seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as u32;
    let mut thread_count = 4;

    for arg in env::args() {
        let arg = arg.split('=').collect::<Vec<&str>>();
        match arg[0] {
            "-s" => {
                seed = arg[1].parse().unwrap();
            },
            "-tc" => {
                thread_count = arg[1].parse().unwrap();
            },
            "-tm" => {
                testing_mode = 1;
                camera_speed = 16.0;
                distant_camera = true;
            }
            _ => {

            }
        }
    }

    let mut scheduler = MasterScheduler::new(seed, thread_count);

    let timestep = Duration::new(0, 16666667);
    let one_second = Duration::new(1, 0);
    let mut time = Duration::new(0, 0);
    let mut prev_time = Instant::now();

    'outer: loop {
        let now = Instant::now();
        time = now - prev_time + time;
        prev_time = now;

        while time > one_second {
            println!("Lagging behind, removing one second of update time");
            time = time - one_second;
        }

        while time > timestep {
            time = time - timestep;

            scheduler.update(camera_position, &display);

            if move_camera {
                camera_position[0] += camera_side_speed;
                camera_position[1] += camera_speed;
            }

            if !distant_camera {
                camera_position[2] = {
                    let pos = (camera_position[0].floor() as i32, camera_position[1].floor() as i32 & !3);
                    let height = {
                        *[scheduler.get_height([pos.0, pos.1 - 4]),
                        scheduler.get_height([pos.0, pos.1 - 3]),
                        scheduler.get_height([pos.0, pos.1 - 2]),
                        scheduler.get_height([pos.0, pos.1 - 1]),
                        scheduler.get_height([pos.0, pos.1]),
                        scheduler.get_height([pos.0, pos.1 + 1]),
                        scheduler.get_height([pos.0, pos.1 + 2]),
                        scheduler.get_height([pos.0, pos.1 + 3])].iter().max().unwrap() + 4
                    };
                    let next_height = {
                        *[scheduler.get_height([pos.0, pos.1]),
                        scheduler.get_height([pos.0, pos.1 + 1]),
                        scheduler.get_height([pos.0, pos.1 + 2]),
                        scheduler.get_height([pos.0, pos.1 + 3]),
                        scheduler.get_height([pos.0, pos.1 + 4]),
                        scheduler.get_height([pos.0, pos.1 + 5]),
                        scheduler.get_height([pos.0, pos.1 + 6]),
                        scheduler.get_height([pos.0, pos.1 + 7])].iter().max().unwrap() + 4
                    };

                    let y_interp = (camera_position[1] - pos.1 as f32)/4.0;

                    ((height as f32) * (1.0 - y_interp) + (next_height as f32) * y_interp).max(camera_position[2]-0.05)
                };
            } else {
                camera_position[2] = 128.0;
            }

            if testing_mode != 0 && camera_position[1] >= 4096.0 {
                match testing_mode {
                    1 => {
                        camera_position[1] = 0.0;
                        testing_mode = 2;
                        scheduler.serially_generate = !scheduler.serially_generate;
                        scheduler.serially_populate = !scheduler.serially_populate;
                        scheduler.serially_mesh = !scheduler.serially_mesh;
                    },
                    2 => {
                        break 'outer;
                    },
                    _ => {
                        unreachable!();
                    }
                }
            }
        }

        let (width, height) = {
            let window = display.get_window().unwrap();
            let size = window.get_inner_size_points().unwrap();
            (size.0 as f32, size.1 as f32)
        };

        let projection = cgmath::perspective(Deg {s: 90.0}, width / height, 0.01, 1000.0f32);

        let model_view = if distant_camera {
            Matrix4::look_at(Point3::from(camera_position), Point3::from(camera_position) + Vector3::new(0.0, 0.0, -1.0f32), Vector3::new(0.0, 1.0, 0.0f32))
        } else {
            Matrix4::look_at(Point3::from(camera_position), Point3::from(camera_position) + Vector3::new(0.0, 1.0, 0.0f32), Vector3::new(0.0, 0.0, 1.0f32))
        };

        let model_view_projection = projection * model_view;

        let mut frame = display.draw();
        frame.clear_color_and_depth((0.0, 0.2, 0.8, 0.0), 1.0);

        scheduler.render(&mut frame, &program, model_view_projection);

        frame.finish().unwrap();

        for event in display.poll_events() {
            match event {
                Event::Closed => return,
                Event::KeyboardInput(state, _, key_code) => {
                    if state == ElementState::Released {
                        if testing_mode != 0 {
                            continue;
                        }
                        if let Some(key_code) = key_code {
                            match key_code {
                                VirtualKeyCode::Key1 => {
                                    println!("Serial generation enabled");
                                    scheduler.serially_generate = true;
                                },
                                VirtualKeyCode::Key2 => {
                                    println!("Parallel generation enabled");
                                    scheduler.serially_generate = false;
                                },
                                VirtualKeyCode::Key3 => {
                                    println!("Serial population enabled");
                                    scheduler.serially_populate = true;
                                },
                                VirtualKeyCode::Key4 => {
                                    println!("Parallel population enabled");
                                    scheduler.serially_populate = false;
                                },
                                VirtualKeyCode::Key5 => {
                                    println!("Serial meshing enabled");
                                    scheduler.serially_mesh = true;
                                },
                                VirtualKeyCode::Key6 => {
                                    println!("Parallel meshing enabled");
                                    scheduler.serially_mesh = false;
                                },
                                VirtualKeyCode::Space => {
                                    move_camera = !move_camera;
                                },
                                VirtualKeyCode::P => {
                                    distant_camera = !distant_camera;
                                    camera_position[2] = 0.0;
                                },
                                VirtualKeyCode::Minus => {
                                    camera_speed -= 0.1;
                                },
                                VirtualKeyCode::Equals => {
                                    camera_speed += 0.1;
                                },
                                VirtualKeyCode::LBracket => {
                                    camera_side_speed -= 0.1;
                                },
                                VirtualKeyCode::RBracket => {
                                    camera_side_speed += 0.1;
                                },
                                VirtualKeyCode::Semicolon => {
                                    camera_side_speed = 0.0;
                                    camera_speed = 0.0;
                                },
                                VirtualKeyCode::Escape => {
                                    break 'outer;
                                },
                                _ => ()
                            }
                        }
                    }
                },
                _ => ()
            }
        }
    }

    println!("SG: {:.2} ms PG: {:.2} ms", scheduler.serial_generator_time, scheduler.parallel_generator_time);
    println!("SP: {:.2} ms PP: {:.2} ms", scheduler.serial_populator_time, scheduler.parallel_populator_time);
    println!("SM: {:.2} ms PM: {:.2} ms", scheduler.serial_meshing_time, scheduler.parallel_meshing_time);
}
<|endoftext|>