<commit_before>//! Helper methods for character counting and entity extraction.
//!
//! According to the official twitter-text Objective-C implementation [(GitHub)][twitter-text], the
//! publicly-exported methods are the following:
//!
//! [twitter-text]: https://github.com/twitter/twitter-text/tree/master/objc
//!
//! * `entitiesInText`: string -> array (presumably all extracted entities)
//! * `urlsInText`: string -> array (presumably all the URLs)
//! * `hashtagsInText`: string, bool -> array (presumably all the hashtags)
//!   * boolean `checkingURLOverlap` parameter extracts URLs first and ignores tags it finds
//!     inside if set (i think?)
//! * `symbolsInText`: string, bool -> array (presumably all the cashtags)
//!   * boolean `checkingURLOverlap` parameter extracts URLs first and ignores tags it finds
//!     inside if set (i think?)
//! * `mentionedScreenNamesInText`: string -> array (presumably all the mentions)
//! * `mentionsOrListsInText`: string -> array (presumably like above, but also @user/list slugs
//!   too)
//! * `repliedScreenNameInText`: string -> single entity (presumably the first mention if in reply
//!   position)
//!
//! * `tweetLength`: string -> int (also includes version with http and https URL lengths)
//! * `remainingCharacterCount`: string -> int (like above, includes alternate version with URL
//!   lengths)

use unicode_normalization::UnicodeNormalization;

mod regexen;

///Represents the kinds of entities that can be extracted from a given text.
#[derive(Debug, PartialEq)]
pub enum EntityKind {
    ///A URL.
    Url,
    ///A user mention.
    ScreenName,
    ///A list mention, in the form "@user/list-name".
    ListName,
    ///A hashtag.
    Hashtag,
    ///A financial symbol ("cashtag").
    Symbol,
}

///Represents an entity extracted from a given text.
#[derive(Debug)]
pub struct Entity {
    ///The kind of entity that was extracted.
    pub kind: EntityKind,
    ///The byte offsets between which the entity text is. The first index indicates the byte at the
    ///beginning of the extracted entity, but the second one is the byte index for the first
    ///character after the extracted entity (or one past the end of the string if the entity was at
    ///the end of the string). For hashtags and symbols, the range includes the # or $ character.
    pub range: (usize, usize),
}

///Parses the given string for URLs.
pub fn url_entities(text: &str) -> Vec<Entity> {
    if text.is_empty() {
        return Vec::new();
    }

    let mut results: Vec<Entity> = Vec::new();
    let mut cursor = 0;

    loop {
        if cursor >= text.len() {
            break;
        }

        //save our matching substring since we modify cursor below
        let substr = &text[cursor..];

        let caps = regexen::RE_SIMPLIFIED_VALID_URL.captures(substr);
        if caps.is_none() {
            break;
        }
        let caps = caps.unwrap();

        if caps.len() < 9 {
            break;
        }

        let current_cursor = cursor;
        cursor += caps.pos(0).unwrap().1;

        let preceding_range = caps.at(2);
        let url_range = caps.pos(3);
        let protocol_range = caps.pos(4);
        let domain_range = caps.pos(5);
        let path_range = caps.pos(7);

        //if protocol is missing and domain contains non-ascii chars, extract ascii-only
        //domains.
        if protocol_range.is_none() {
            if let Some(preceding) = preceding_range {
                if regexen::RE_URL_WO_PROTOCOL_INVALID_PRECEDING_CHARS.is_match(preceding) {
                    continue;
                }
            }

            if domain_range.is_none() { continue; }
            let mut domain_range = domain_range.unwrap();

            let mut loop_inserted = false;

            while domain_range.0 < domain_range.1 {
                //include succeeding character for validation
                let extra_char = if let Some(ch) = text[(cursor+domain_range.1)..].chars().next() {
                    ch.len_utf8()
                }
                else {
                    0
                };

                let url_range: (usize, usize);

                if let Some(caps) = regexen::RE_VALID_ASCII_DOMAIN.captures(&text[(current_cursor+domain_range.0)..(current_cursor+domain_range.1+extra_char)]) {
                    if let Some(range) = caps.pos(1) {
                        url_range = range;
                    }
                    else {
                        break;
                    }
                }
                else {
                    break;
                }

                loop_inserted = true;

                if path_range.is_some() ||
                   regexen::RE_VALID_SPECIAL_SHORT_DOMAIN.is_match(&substr[url_range.0..url_range.1]) ||
                   !regexen::RE_INVALID_SHORT_DOMAIN.is_match(&substr[url_range.0..url_range.1])
                {
                    results.push(Entity {
                        kind: EntityKind::Url,
                        range: url_range,
                    });
                }

                domain_range.0 = url_range.1;
            }

            if !loop_inserted {
                continue;
            }

            if let Some(last_entity) = results.last_mut() {
                if let Some(path_range) = path_range {
                    if last_entity.range.1 == path_range.0 {
                        last_entity.range.1 += path_range.1 - path_range.0;
                    }
                }

                cursor = last_entity.range.1;
            }
        }
        else {
            if url_range.is_none() { continue; }
            if domain_range.is_none() { continue; }
            let mut url_range = url_range.unwrap();
            let domain_range = domain_range.unwrap();

            //in case of t.co URLs, don't allow additional path characters
            if let Some((_, to)) = regexen::RE_VALID_TCO_URL.find(&substr[url_range.0..url_range.1]) {
                url_range.1 = to;
            }
            else if !regexen::RE_URL_FOR_VALIDATION.is_match(&substr[domain_range.0..domain_range.1]) {
                continue;
            }

            results.push(Entity {
                kind: EntityKind::Url,
                range: (url_range.0 + current_cursor, url_range.1 + current_cursor),
            });
        }
    }

    results
}

///Parses the given string for user and list mentions.
pub fn mention_list_entities(text: &str) -> Vec<Entity> {
    if text.is_empty() {
        return Vec::new();
    }

    let mut results = Vec::new();
    let mut cursor = 0usize;

    loop {
        if cursor >= text.len() {
            break;
        }

        //save our matching substring since we modify cursor below
        let substr = &text[cursor..];

        let caps = regexen::RE_VALID_MENTION_OR_LIST.captures(substr);
        if caps.is_none() {
            break;
        }
        let caps = caps.unwrap();

        if caps.len() < 5 {
            break;
        }

        let current_cursor = cursor;
        cursor += caps.pos(0).unwrap().1;

        if !regexen::RE_END_MENTION.is_match(&text[cursor..]) {
            let at_sign_range = caps.pos(2);
            let screen_name_range = caps.pos(3);
            let list_name_range = caps.pos(4);

            if at_sign_range.is_none() {
                continue;
            }
            let at_sign_range = at_sign_range.unwrap();

            if let Some((_, end)) = list_name_range {
                results.push(Entity {
                    kind: EntityKind::ListName,
                    range: (current_cursor + at_sign_range.0, current_cursor + end),
                });
            }
            else if let Some((_, end)) = screen_name_range {
                results.push(Entity {
                    kind: EntityKind::ScreenName,
                    range: (current_cursor + at_sign_range.0, current_cursor + end),
                });
            }
        }
        else {
            //Avoid matching the second username in @username@username
            cursor += 1;
        }
    }

    results
}

///Parses the given string for user mentions.
pub fn mention_entities(text: &str) -> Vec<Entity> {
    let mut results = mention_list_entities(text);

    results.retain(|e| e.kind == EntityKind::ScreenName);

    results
}

///Parses the given string for hashtags, optionally leaving out those that are part of URLs.
pub fn hashtag_entities(text: &str, check_url_overlap: bool) -> Vec<Entity> {
    if text.is_empty() {
        return Vec::new();
    }

    let url_entities = if check_url_overlap {
        url_entities(text)
    }
    else {
        Vec::new()
    };

    extract_hashtags(text, &url_entities)
}

fn extract_hashtags(text: &str, url_entities: &[Entity]) -> Vec<Entity> {
    if text.is_empty() {
        return Vec::new();
    }

    let mut results = Vec::new();
    let mut cursor = 0usize;

    loop {
        if cursor >= text.len() {
            break;
        }

        let substr = &text[cursor..];

        let caps = regexen::RE_VALID_HASHTAG.captures(substr);
        if caps.is_none() {
            break;
        }
        let caps = caps.unwrap();

        if caps.len() < 3 {
            break;
        }

        let current_cursor = cursor;
        cursor += caps.pos(0).unwrap().1;

        let hashtag_range = caps.pos(1);
        let text_range = caps.pos(2);

        if hashtag_range.is_none() {
            break;
        }
        let hashtag_range = hashtag_range.unwrap();

        if text_range.is_none() {
            break;
        }
        let text_range = text_range.unwrap();

        //note: check character after the # to make sure it's not \u{fe0f} or \u{20e3}
        //this is because the regex crate doesn't have lookahead assertions, which the objc impl
        //used to check for this
        if regexen::RE_HASHTAG_INVALID_INITIAL_CHARS.is_match(&substr[text_range.0..text_range.1]) {
            break;
        }

        let mut match_ok = true;

        for url in url_entities {
            if (hashtag_range.0 + current_cursor) <= url.range.1 &&
                url.range.0 <= (hashtag_range.1 + current_cursor)
            {
                //this hashtag is part of a url in the same text, skip it
                match_ok = false;
                break;
            }
        }

        if match_ok {
            if regexen::RE_END_HASHTAG.is_match(&substr[hashtag_range.1..]) {
                match_ok = false;
            }
        }

        if match_ok {
            results.push(Entity {
                kind: EntityKind::Hashtag,
                range: (hashtag_range.0 + current_cursor, hashtag_range.1 + current_cursor),
            });
        }
    }

    results
}

///Parses the given string for financial symbols ("cashtags"), optionally leaving out those that
///are part of URLs.
pub fn symbol_entities(text: &str, check_url_overlap: bool) -> Vec<Entity> {
    if text.is_empty() {
        return Vec::new();
    }

    let url_entities = if check_url_overlap {
        url_entities(text)
    }
    else {
        Vec::new()
    };

    extract_symbols(text, &url_entities)
}

fn extract_symbols(text: &str, url_entities: &[Entity]) -> Vec<Entity> {
    if text.is_empty() {
        return Vec::new();
    }

    let mut results = Vec::new();
    let mut cursor = 0usize;

    loop {
        if cursor >= text.len() {
            break;
        }

        let substr = &text[cursor..];

        let caps = if let Some(caps) = regexen::RE_VALID_SYMBOL.captures(substr) {
            caps
        } else { break; };

        if caps.len() < 2 { break; }

        let symbol_range = if let Some(range) = caps.pos(1) {
            range
        } else { break; };

        let current_cursor = cursor;
        cursor += symbol_range.1;

        let mut match_ok = true;

        for url in url_entities {
            if (symbol_range.0 + current_cursor) <= url.range.1 &&
                url.range.0 <= (symbol_range.1 + current_cursor)
            {
                //this symbol is part of a url in the same text, skip it
                match_ok = false;
                break;
            }
        }

        if match_ok {
            results.push(Entity {
                kind: EntityKind::Symbol,
                range: (symbol_range.0 + current_cursor, symbol_range.1 + current_cursor),
            });
        }
    }

    results
}

///Returns how many characters the given text would be, after accounting for URL shortening.
pub fn character_count(text: &str, http_url_len: i32, https_url_len: i32) -> usize {
    //twitter uses code point counts after NFC normalization
    let mut text = text.nfc().collect::<String>();

    if text.is_empty() {
        return 0;
    }

    let mut url_offset = 0usize;
    let entities = url_entities(&text);

    for url in &entities {
        let substr = &text[url.range.0..url.range.1];
        if substr.contains("https") {
            url_offset += https_url_len as usize;
        }
        else {
            url_offset += http_url_len as usize;
        }
    }

    //put character removal in a second pass so we don't mess up the byte offsets
    for url in entities.iter().rev() {
        text.drain(url.range.0..url.range.1);
    }

    text.len() + url_offset
}

///Returns how many characters would remain in a traditional 140-character tweet with the given
///text.
pub fn characters_remaining(text: &str, http_url_len: i32, https_url_len: i32) -> usize {
    140 - character_count(text, http_url_len, https_url_len)
}
<commit_msg>add convience macros so i don't unwrap captures all the time<commit_after>//! Helper methods for character counting and entity extraction.
//!
//! According to the official twitter-text Objective-C implementation [(GitHub)][twitter-text], the
//! publicly-exported methods are the following:
//!
//! [twitter-text]: https://github.com/twitter/twitter-text/tree/master/objc
//!
//! * `entitiesInText`: string -> array (presumably all extracted entities)
//! * `urlsInText`: string -> array (presumably all the URLs)
//! * `hashtagsInText`: string, bool -> array (presumably all the hashtags)
//!   * boolean `checkingURLOverlap` parameter extracts URLs first and ignores tags it finds
//!     inside if set (i think?)
//! * `symbolsInText`: string, bool -> array (presumably all the cashtags)
//!   * boolean `checkingURLOverlap` parameter extracts URLs first and ignores tags it finds
//!     inside if set (i think?)
//! * `mentionedScreenNamesInText`: string -> array (presumably all the mentions)
//! * `mentionsOrListsInText`: string -> array (presumably like above, but also @user/list slugs
//!   too)
//! * `repliedScreenNameInText`: string -> single entity (presumably the first mention if in reply
//!   position)
//!
//! * `tweetLength`: string -> int (also includes version with http and https URL lengths)
//! * `remainingCharacterCount`: string -> int (like above, includes alternate version with URL
//!   lengths)

///A convenience macro to break loops if the given value is `None`.
macro_rules! break_opt {
    ($input:expr) => {{
        if let Some(val) = $input {
            val
        }
        else { break; }
    }};
}

///A convenience macro to continue loops if the given value is `None`.
macro_rules! continue_opt {
    ($input:expr) => {{
        if let Some(val) = $input {
            val
        }
        else { continue; }
    }};
}

use unicode_normalization::UnicodeNormalization;

mod regexen;

///Represents the kinds of entities that can be extracted from a given text.
#[derive(Debug, PartialEq)]
pub enum EntityKind {
    ///A URL.
    Url,
    ///A user mention.
    ScreenName,
    ///A list mention, in the form "@user/list-name".
    ListName,
    ///A hashtag.
    Hashtag,
    ///A financial symbol ("cashtag").
    Symbol,
}

///Represents an entity extracted from a given text.
#[derive(Debug)]
pub struct Entity {
    ///The kind of entity that was extracted.
    pub kind: EntityKind,
    ///The byte offsets between which the entity text is. The first index indicates the byte at the
    ///beginning of the extracted entity, but the second one is the byte index for the first
    ///character after the extracted entity (or one past the end of the string if the entity was at
    ///the end of the string). For hashtags and symbols, the range includes the # or $ character.
    pub range: (usize, usize),
}

///Parses the given string for URLs.
pub fn url_entities(text: &str) -> Vec<Entity> {
    if text.is_empty() {
        return Vec::new();
    }

    let mut results: Vec<Entity> = Vec::new();
    let mut cursor = 0;

    loop {
        if cursor >= text.len() {
            break;
        }

        //save our matching substring since we modify cursor below
        let substr = &text[cursor..];

        let caps = break_opt!(regexen::RE_SIMPLIFIED_VALID_URL.captures(substr));

        if caps.len() < 9 {
            break;
        }

        let current_cursor = cursor;
        cursor += caps.pos(0).unwrap().1;

        let preceding_range = caps.at(2);
        let url_range = caps.pos(3);
        let protocol_range = caps.pos(4);
        let domain_range = caps.pos(5);
        let path_range = caps.pos(7);

        //if protocol is missing and domain contains non-ascii chars, extract ascii-only
        //domains.
        if protocol_range.is_none() {
            if let Some(preceding) = preceding_range {
                if regexen::RE_URL_WO_PROTOCOL_INVALID_PRECEDING_CHARS.is_match(preceding) {
                    continue;
                }
            }

            let mut domain_range = continue_opt!(domain_range);

            let mut loop_inserted = false;

            while domain_range.0 < domain_range.1 {
                //include succeeding character for validation
                let extra_char = if let Some(ch) = text[(cursor+domain_range.1)..].chars().next() {
                    ch.len_utf8()
                }
                else {
                    0
                };

                let url_range: (usize, usize);

                if let Some(caps) = regexen::RE_VALID_ASCII_DOMAIN.captures(&text[(current_cursor+domain_range.0)..(current_cursor+domain_range.1+extra_char)]) {
                    if let Some(range) = caps.pos(1) {
                        url_range = range;
                    }
                    else {
                        break;
                    }
                }
                else {
                    break;
                }

                loop_inserted = true;

                if path_range.is_some() ||
                   regexen::RE_VALID_SPECIAL_SHORT_DOMAIN.is_match(&substr[url_range.0..url_range.1]) ||
                   !regexen::RE_INVALID_SHORT_DOMAIN.is_match(&substr[url_range.0..url_range.1])
                {
                    results.push(Entity {
                        kind: EntityKind::Url,
                        range: url_range,
                    });
                }

                domain_range.0 = url_range.1;
            }

            if !loop_inserted {
                continue;
            }

            if let Some(last_entity) = results.last_mut() {
                if let Some(path_range) = path_range {
                    if last_entity.range.1 == path_range.0 {
                        last_entity.range.1 += path_range.1 - path_range.0;
                    }
                }

                cursor = last_entity.range.1;
            }
        }
        else {
            let mut url_range = continue_opt!(url_range);
            let domain_range = continue_opt!(domain_range);

            //in case of t.co URLs, don't allow additional path characters
            if let Some((_, to)) = regexen::RE_VALID_TCO_URL.find(&substr[url_range.0..url_range.1]) {
                url_range.1 = to;
            }
            else if !regexen::RE_URL_FOR_VALIDATION.is_match(&substr[domain_range.0..domain_range.1]) {
                continue;
            }

            results.push(Entity {
                kind: EntityKind::Url,
                range: (url_range.0 + current_cursor, url_range.1 + current_cursor),
            });
        }
    }

    results
}

///Parses the given string for user and list mentions.
pub fn mention_list_entities(text: &str) -> Vec<Entity> {
    if text.is_empty() {
        return Vec::new();
    }

    let mut results = Vec::new();
    let mut cursor = 0usize;

    loop {
        if cursor >= text.len() {
            break;
        }

        //save our matching substring since we modify cursor below
        let substr = &text[cursor..];

        let caps = break_opt!(regexen::RE_VALID_MENTION_OR_LIST.captures(substr));

        if caps.len() < 5 {
            break;
        }

        let current_cursor = cursor;
        cursor += caps.pos(0).unwrap().1;

        if !regexen::RE_END_MENTION.is_match(&text[cursor..]) {
            let at_sign_range = continue_opt!(caps.pos(2));
            let screen_name_range = caps.pos(3);
            let list_name_range = caps.pos(4);

            if let Some((_, end)) = list_name_range {
                results.push(Entity {
                    kind: EntityKind::ListName,
                    range: (current_cursor + at_sign_range.0, current_cursor + end),
                });
            }
            else if let Some((_, end)) = screen_name_range {
                results.push(Entity {
                    kind: EntityKind::ScreenName,
                    range: (current_cursor + at_sign_range.0, current_cursor + end),
                });
            }
        }
        else {
            //Avoid matching the second username in @username@username
            cursor += 1;
        }
    }

    results
}

///Parses the given string for user mentions.
pub fn mention_entities(text: &str) -> Vec<Entity> {
    let mut results = mention_list_entities(text);

    results.retain(|e| e.kind == EntityKind::ScreenName);

    results
}

///Parses the given string for hashtags, optionally leaving out those that are part of URLs.
pub fn hashtag_entities(text: &str, check_url_overlap: bool) -> Vec<Entity> {
    if text.is_empty() {
        return Vec::new();
    }

    let url_entities = if check_url_overlap {
        url_entities(text)
    }
    else {
        Vec::new()
    };

    extract_hashtags(text, &url_entities)
}

fn extract_hashtags(text: &str, url_entities: &[Entity]) -> Vec<Entity> {
    if text.is_empty() {
        return Vec::new();
    }

    let mut results = Vec::new();
    let mut cursor = 0usize;

    loop {
        if cursor >= text.len() {
            break;
        }

        let substr = &text[cursor..];

        let caps = break_opt!(regexen::RE_VALID_HASHTAG.captures(substr));

        if caps.len() < 3 {
            break;
        }

        let current_cursor = cursor;
        cursor += caps.pos(0).unwrap().1;

        let hashtag_range = break_opt!(caps.pos(1));
        let text_range = break_opt!(caps.pos(2));

        //note: check character after the # to make sure it's not \u{fe0f} or \u{20e3}
        //this is because the regex crate doesn't have lookahead assertions, which the objc impl
        //used to check for this
        if regexen::RE_HASHTAG_INVALID_INITIAL_CHARS.is_match(&substr[text_range.0..text_range.1]) {
            break;
        }

        let mut match_ok = true;

        for url in url_entities {
            if (hashtag_range.0 + current_cursor) <= url.range.1 &&
                url.range.0 <= (hashtag_range.1 + current_cursor)
            {
                //this hashtag is part of a url in the same text, skip it
                match_ok = false;
                break;
            }
        }

        if match_ok {
            if regexen::RE_END_HASHTAG.is_match(&substr[hashtag_range.1..]) {
                match_ok = false;
            }
        }

        if match_ok {
            results.push(Entity {
                kind: EntityKind::Hashtag,
                range: (hashtag_range.0 + current_cursor, hashtag_range.1 + current_cursor),
            });
        }
    }

    results
}

///Parses the given string for financial symbols ("cashtags"), optionally leaving out those that
///are part of URLs.
pub fn symbol_entities(text: &str, check_url_overlap: bool) -> Vec<Entity> {
    if text.is_empty() {
        return Vec::new();
    }

    let url_entities = if check_url_overlap {
        url_entities(text)
    }
    else {
        Vec::new()
    };

    extract_symbols(text, &url_entities)
}

fn extract_symbols(text: &str, url_entities: &[Entity]) -> Vec<Entity> {
    if text.is_empty() {
        return Vec::new();
    }

    let mut results = Vec::new();
    let mut cursor = 0usize;

    loop {
        if cursor >= text.len() {
            break;
        }

        let substr = &text[cursor..];

        let caps = break_opt!(regexen::RE_VALID_SYMBOL.captures(substr));

        if caps.len() < 2 { break; }

        let symbol_range = break_opt!(caps.pos(1));

        let current_cursor = cursor;
        cursor += symbol_range.1;

        let mut match_ok = true;

        for url in url_entities {
            if (symbol_range.0 + current_cursor) <= url.range.1 &&
                url.range.0 <= (symbol_range.1 + current_cursor)
            {
                //this symbol is part of a url in the same text, skip it
                match_ok = false;
                break;
            }
        }

        if match_ok {
            results.push(Entity {
                kind: EntityKind::Symbol,
                range: (symbol_range.0 + current_cursor, symbol_range.1 + current_cursor),
            });
        }
    }

    results
}

///Returns how many characters the given text would be, after accounting for URL shortening.
pub fn character_count(text: &str, http_url_len: i32, https_url_len: i32) -> usize {
    //twitter uses code point counts after NFC normalization
    let mut text = text.nfc().collect::<String>();

    if text.is_empty() {
        return 0;
    }

    let mut url_offset = 0usize;
    let entities = url_entities(&text);

    for url in &entities {
        let substr = &text[url.range.0..url.range.1];
        if substr.contains("https") {
            url_offset += https_url_len as usize;
        }
        else {
            url_offset += http_url_len as usize;
        }
    }

    //put character removal in a second pass so we don't mess up the byte offsets
    for url in entities.iter().rev() {
        text.drain(url.range.0..url.range.1);
    }

    text.len() + url_offset
}

///Returns how many characters would remain in a traditional 140-character tweet with the given
///text.
pub fn characters_remaining(text: &str, http_url_len: i32, https_url_len: i32) -> usize {
    140 - character_count(text, http_url_len, https_url_len)
}
<|endoftext|>


<commit_before>//! # TreeView Sample
//!
//! This sample demonstrates how to create a TreeView with either a ListStore or TreeStore.

extern crate glib;
extern crate gtk;
extern crate gdk_pixbuf;

use gtk::prelude::*;
use gdk_pixbuf::Pixbuf;

fn append_text_column(tree: &gtk::TreeView) {
    let column = gtk::TreeViewColumn::new();
    let cell = gtk::CellRendererText::new();

    column.pack_start(&cell, true);
    column.add_attribute(&cell, "text", 0);
    tree.append_column(&column);
}

fn main() {
    if gtk::init().is_err() {
        println!("Failed to initialize GTK.");
        return;
    }

    let window = gtk::Window::new(gtk::WindowType::Toplevel);

    window.set_title("TreeView Sample");
    window.set_position(gtk::WindowPosition::Center);

    window.connect_delete_event(|_, _| {
        gtk::main_quit();
        Inhibit(false)
    });

    // left pane

    let left_tree = gtk::TreeView::new();
    let column_types = [String::static_type()];
    let left_store = gtk::ListStore::new(&column_types);

    left_tree.set_model(Some(&left_store));
    left_tree.set_headers_visible(false);
    append_text_column(&left_tree);

    for _ in 0..10 {
        left_store.insert_with_values(None, &[0], &[&"I'm in a list"]);
    }

    // middle pane

    let middle_tree = gtk::TreeView::new();
    let column_types = [String::static_type()];
    let middle_store = gtk::TreeStore::new(&column_types);

    middle_tree.set_model(Some(&middle_store));
    middle_tree.set_headers_visible(false);
    append_text_column(&middle_tree);

    for i in 0..10 {
        // insert_with_values takes two slices: column indices and ToValue
        // trait objects. ToValue is implemented for strings, numeric types,
        // bool and Object descendants
        let iter = middle_store.insert_with_values(None, None, &[0], &[&format!("Hello {}", i)]);

        for _ in 0..i {
            middle_store.insert_with_values(Some(&iter), None, &[0], &[&"I'm a child node"]);
        }
    }

    // right pane
    let right_tree = gtk::TreeView::new();
    let right_column_types = [Pixbuf::static_type(), String::static_type()];
    let right_store = gtk::TreeStore::new(&right_column_types);
    let renderer = gtk::CellRendererPixbuf::new();
    let col = gtk::TreeViewColumn::new();

    col.set_title("Picture");
    col.pack_start(&renderer, false);

    col.add_attribute(&renderer, "pixbuf", 0);

    let renderer2 = gtk::CellRendererText::new();
    col.pack_start(&renderer2, true);
    col.add_attribute(&renderer2, "text", 1);
    let image = match Pixbuf::new_from_file("./resources/eye.png") {
        Ok(i) => i,
        Err(e) => {
            println!("Error while creating image: {}", e);
            if e.kind() == Some(glib::FileError::Noent) {
                println!("Relaunch this example from the same level as the `resources` folder");
            }
            return;
        }
    };

    right_tree.append_column(&col);
    right_tree.set_model(Some(&right_store));
    right_tree.set_headers_visible(true);

    for _ in 0..10 {
        right_store.insert_with_values(None, None, &[0, 1],
                                       &[&image, &"I'm a child node with an image"]);
    }

    // selection and path manipulation

    let left_selection = left_tree.get_selection();
    let right_tree1 = right_tree.clone();
    left_selection.connect_changed(move |tree_selection| {
        let (left_model, iter) = tree_selection.get_selected().unwrap();
        let mut path = left_model.get_path(&iter);
        // get the top-level element path
        while path.get_depth() > 1 {
            path.up();
        }
        right_tree1.get_selection().select_path(&path);
    });

    // display the panes

    let split_pane = gtk::Box::new(gtk::Orientation::Horizontal, 10);

    split_pane.set_size_request(-1, -1);
    split_pane.add(&left_tree);
    split_pane.add(&middle_tree);
    split_pane.add(&right_tree);

    window.add(&split_pane);
    window.show_all();
    gtk::main();
}
<commit_msg>Show a message dialog on pixbuf error<commit_after>//! # TreeView Sample
//!
//! This sample demonstrates how to create a TreeView with either a ListStore or TreeStore.

extern crate glib;
extern crate gtk;
extern crate gdk_pixbuf;

use gtk::prelude::*;
use gdk_pixbuf::Pixbuf;

fn append_text_column(tree: &gtk::TreeView) {
    let column = gtk::TreeViewColumn::new();
    let cell = gtk::CellRendererText::new();

    column.pack_start(&cell, true);
    column.add_attribute(&cell, "text", 0);
    tree.append_column(&column);
}

fn main() {
    if gtk::init().is_err() {
        println!("Failed to initialize GTK.");
        return;
    }

    let window = gtk::Window::new(gtk::WindowType::Toplevel);

    window.set_title("TreeView Sample");
    window.set_position(gtk::WindowPosition::Center);

    window.connect_delete_event(|_, _| {
        gtk::main_quit();
        Inhibit(false)
    });

    // left pane

    let left_tree = gtk::TreeView::new();
    let column_types = [String::static_type()];
    let left_store = gtk::ListStore::new(&column_types);

    left_tree.set_model(Some(&left_store));
    left_tree.set_headers_visible(false);
    append_text_column(&left_tree);

    for _ in 0..10 {
        left_store.insert_with_values(None, &[0], &[&"I'm in a list"]);
    }

    // middle pane

    let middle_tree = gtk::TreeView::new();
    let column_types = [String::static_type()];
    let middle_store = gtk::TreeStore::new(&column_types);

    middle_tree.set_model(Some(&middle_store));
    middle_tree.set_headers_visible(false);
    append_text_column(&middle_tree);

    for i in 0..10 {
        // insert_with_values takes two slices: column indices and ToValue
        // trait objects. ToValue is implemented for strings, numeric types,
        // bool and Object descendants
        let iter = middle_store.insert_with_values(None, None, &[0], &[&format!("Hello {}", i)]);

        for _ in 0..i {
            middle_store.insert_with_values(Some(&iter), None, &[0], &[&"I'm a child node"]);
        }
    }

    // right pane
    let right_tree = gtk::TreeView::new();
    let right_column_types = [Pixbuf::static_type(), String::static_type()];
    let right_store = gtk::TreeStore::new(&right_column_types);
    let renderer = gtk::CellRendererPixbuf::new();
    let col = gtk::TreeViewColumn::new();

    col.set_title("Picture");
    col.pack_start(&renderer, false);

    col.add_attribute(&renderer, "pixbuf", 0);

    let renderer2 = gtk::CellRendererText::new();
    col.pack_start(&renderer2, true);
    col.add_attribute(&renderer2, "text", 1);
    let image = Pixbuf::new_from_file("./resources/eye.png").or_else(|err| {
        let mut msg = err.to_string();
        if err.kind() == Some(glib::FileError::Noent) {
            msg.push_str(&format!("\nRelaunch this example from the same level \
                                  as the `resources` folder"));
        }
        let window = window.clone();

        gtk::idle_add(move || {
            let dialog = gtk::MessageDialog::new(Some(&window), gtk::DIALOG_MODAL,
                gtk::MessageType::Error, gtk::ButtonsType::Ok, &msg);
            dialog.run();
            dialog.destroy();
            Continue(false)
        });

        Err(())
    }).ok();

    right_tree.append_column(&col);
    right_tree.set_model(Some(&right_store));
    right_tree.set_headers_visible(true);

    for _ in 0..10 {
        right_store.insert_with_values(None, None, &[0, 1],
                                       &[&image, &"I'm a child node with an image"]);
    }

    // selection and path manipulation

    let left_selection = left_tree.get_selection();
    let right_tree1 = right_tree.clone();
    left_selection.connect_changed(move |tree_selection| {
        let (left_model, iter) = tree_selection.get_selected().unwrap();
        let mut path = left_model.get_path(&iter);
        // get the top-level element path
        while path.get_depth() > 1 {
            path.up();
        }
        right_tree1.get_selection().select_path(&path);
    });

    // display the panes

    let split_pane = gtk::Box::new(gtk::Orientation::Horizontal, 10);

    split_pane.set_size_request(-1, -1);
    split_pane.add(&left_tree);
    split_pane.add(&middle_tree);
    split_pane.add(&right_tree);

    window.add(&split_pane);
    window.show_all();
    gtk::main();
}
<|endoftext|>


<commit_before>use utils::is_stop_char;
use std::collections::HashMap;

pub fn get_trigrams_with_positions(text : &str) -> HashMap<String, u32> {
    let counter_hash = count(text);

    // Sort in descending order by number of occurrences and trigrams
    let mut count_vec: Vec<_> = counter_hash.into_iter().map(|(trigram, count)| (count, trigram)).collect();
    count_vec.sort_by(|a, b| b.cmp(a));

    // TODO: extract 600 as LANG_PROFILE_LENGTH * 2
    count_vec.into_iter().take(600).enumerate().map(|(i, (_, trigram))| (trigram, i as u32)).collect()
}

#[inline(always)]
fn count(text : &str) -> HashMap<String, u32> {
    let mut s = text.to_lowercase();
    s.push(' '); // add space to the end

    let mut counter_hash : HashMap<String, u32> = HashMap::new();

    // iterate through the string and count trigrams
    let mut chars_iter = s.chars().map(to_trigram_char);
    let mut c1 = ' ';
    let mut c2 = chars_iter.next().unwrap();
    for cur_char in chars_iter {
        let c3 = cur_char;
        if !(c2 == ' ' && (c1 == ' ' || c3 == ' ')) {
            let mut trigram = String::with_capacity(3);
            trigram.push(c1);
            trigram.push(c2);
            trigram.push(c3);
            let count = counter_hash.entry(trigram).or_insert(0);
            *count += 1;
        }
        c1 = c2;
        c2 = c3;
    }

    counter_hash
}

// Convert punctuations and digits to a space.
#[inline(always)]
fn to_trigram_char(ch : char) -> char {
    if is_stop_char(ch) { ' ' } else { ch }
}

#[cfg(test)]
mod tests {
    use super::to_trigram_char;
    use super::count;
    use super::get_trigrams_with_positions;

    fn assert_valuable_trigram_chars(chars : &[char]) {
        for &ch in chars.iter() {
            assert_eq!(to_trigram_char(ch), ch);
        }
    }

    fn assert_not_valuable_trigram_chars(chars : &[char]) {
        for &ch in chars.iter() {
            assert_eq!(to_trigram_char(ch), ' ');
        }
    }

    #[test]
    fn test_to_trigram_char() {
        // valuable chars, that metters
        assert_valuable_trigram_chars(&['a', 'z', 'A', 'Z', 'Ж', 'ß']);

        // punctuations, digits, etc..
        //
        // 0x00 - 0x40
        assert_not_valuable_trigram_chars(&['\t', '\n', ' ', '.', '0', '9', ',', '@']);
        // 0x5B - 0x60
        assert_not_valuable_trigram_chars(&['[', ']', '^', '\\', '`']);
        // 0x7B - 0x7E
        assert_not_valuable_trigram_chars(&['[', '|', '{', '}', '~']);
    }

    fn assert_count(text : &str, pairs : &[(&str, u32)]) {
        let result = count(&text.to_string());
        for &(k, v) in pairs.iter() {
            let &actual_val = result.get(k).unwrap_or(&0);
            assert_eq!(actual_val, v, "trigram '{}' expected to occur {} times, got {}", k, v, actual_val);
        }
        assert_eq!(result.len(), pairs.len());
    }

    #[test]
    fn test_count() {
        assert_count("", &[]);
        assert_count(",", &[]);
        assert_count("a", &[(" a ", 1)]);
        assert_count("-a-", &[(" a ", 1)]);
        assert_count("yes", &[(" ye", 1), ("yes", 1), ("es ", 1)]);
        assert_count("Give - IT...", &[(" gi", 1), ("giv", 1), ("ive", 1), ("ve ", 1), (" it", 1), ("it ", 1)]);
    }

    #[test]
    fn test_get_trigrams_with_positions() {
        let res = get_trigrams_with_positions(&"xaaaaabbbbd".to_string());
        assert_eq!(*res.get("aaa").unwrap(), 0);
        assert_eq!(*res.get("bbb").unwrap(), 1);
    }
}
<commit_msg>Preallocate trigram with sum of utf8 sizes of all chars<commit_after>use utils::is_stop_char;
use std::collections::HashMap;

pub fn get_trigrams_with_positions(text : &str) -> HashMap<String, u32> {
    let counter_hash = count(text);

    // Sort in descending order by number of occurrences and trigrams
    let mut count_vec: Vec<_> = counter_hash.into_iter().map(|(trigram, count)| (count, trigram)).collect();
    count_vec.sort_by(|a, b| b.cmp(a));

    // TODO: extract 600 as LANG_PROFILE_LENGTH * 2
    count_vec.into_iter().take(600).enumerate().map(|(i, (_, trigram))| (trigram, i as u32)).collect()
}

#[inline(always)]
fn count(text : &str) -> HashMap<String, u32> {
    let mut s = text.to_lowercase();
    s.push(' '); // add space to the end

    let mut counter_hash : HashMap<String, u32> = HashMap::new();

    // iterate through the string and count trigrams
    let mut chars_iter = s.chars().map(to_trigram_char);
    let mut c1 = ' ';
    let mut c2 = chars_iter.next().unwrap();
    for cur_char in chars_iter {
        let c3 = cur_char;
        if !(c2 == ' ' && (c1 == ' ' || c3 == ' ')) {
            let mut trigram = String::with_capacity(c1.len_utf8() + c2.len_utf8() + c3.len_utf8());
            trigram.push(c1);
            trigram.push(c2);
            trigram.push(c3);
            let count = counter_hash.entry(trigram).or_insert(0);
            *count += 1;
        }
        c1 = c2;
        c2 = c3;
    }

    counter_hash
}

// Convert punctuations and digits to a space.
#[inline(always)]
fn to_trigram_char(ch : char) -> char {
    if is_stop_char(ch) { ' ' } else { ch }
}

#[cfg(test)]
mod tests {
    use super::to_trigram_char;
    use super::count;
    use super::get_trigrams_with_positions;

    fn assert_valuable_trigram_chars(chars : &[char]) {
        for &ch in chars.iter() {
            assert_eq!(to_trigram_char(ch), ch);
        }
    }

    fn assert_not_valuable_trigram_chars(chars : &[char]) {
        for &ch in chars.iter() {
            assert_eq!(to_trigram_char(ch), ' ');
        }
    }

    #[test]
    fn test_to_trigram_char() {
        // valuable chars, that metters
        assert_valuable_trigram_chars(&['a', 'z', 'A', 'Z', 'Ж', 'ß']);

        // punctuations, digits, etc..
        //
        // 0x00 - 0x40
        assert_not_valuable_trigram_chars(&['\t', '\n', ' ', '.', '0', '9', ',', '@']);
        // 0x5B - 0x60
        assert_not_valuable_trigram_chars(&['[', ']', '^', '\\', '`']);
        // 0x7B - 0x7E
        assert_not_valuable_trigram_chars(&['[', '|', '{', '}', '~']);
    }

    fn assert_count(text : &str, pairs : &[(&str, u32)]) {
        let result = count(&text.to_string());
        for &(k, v) in pairs.iter() {
            let &actual_val = result.get(k).unwrap_or(&0);
            assert_eq!(actual_val, v, "trigram '{}' expected to occur {} times, got {}", k, v, actual_val);
        }
        assert_eq!(result.len(), pairs.len());
    }

    #[test]
    fn test_count() {
        assert_count("", &[]);
        assert_count(",", &[]);
        assert_count("a", &[(" a ", 1)]);
        assert_count("-a-", &[(" a ", 1)]);
        assert_count("yes", &[(" ye", 1), ("yes", 1), ("es ", 1)]);
        assert_count("Give - IT...", &[(" gi", 1), ("giv", 1), ("ive", 1), ("ve ", 1), (" it", 1), ("it ", 1)]);
    }

    #[test]
    fn test_get_trigrams_with_positions() {
        let res = get_trigrams_with_positions(&"xaaaaabbbbd".to_string());
        assert_eq!(*res.get("aaa").unwrap(), 0);
        assert_eq!(*res.get("bbb").unwrap(), 1);
    }
}
<|endoftext|>


<commit_before>use std;
use std::io::{Chars, Read, Write};
use std::sync;
use std::sync::atomic;
use libc;
use nix;
use nix::sys::signal;
use nix::sys::termios;

use consts::{self, KeyPress};
use ::Result;
use ::error;
use super::RawReader;

pub type Mode = termios::Termios;
const STDIN_FILENO: libc::c_int = libc::STDIN_FILENO;
const STDOUT_FILENO: libc::c_int = libc::STDOUT_FILENO;

/// Unsupported Terminals that don't support RAW mode
static UNSUPPORTED_TERM: [&'static str; 3] = ["dumb", "cons25", "emacs"];

#[cfg(any(target_os = "macos", target_os = "freebsd"))]
const TIOCGWINSZ: libc::c_ulong = 0x40087468;

#[cfg(any(all(target_os = "linux", target_env = "gnu"), target_os = "android"))]
const TIOCGWINSZ: libc::c_ulong = 0x5413;

#[cfg(all(target_os = "linux", target_env = "musl"))]
const TIOCGWINSZ: libc::c_int = 0x5413;

/// Try to get the number of columns in the current terminal,
/// or assume 80 if it fails.
fn get_columns() -> usize {
    let (cols, _) = get_win_size();
    cols
}

/// Try to get the number of rows in the current terminal,
/// or assume 24 if it fails.
fn get_rows() -> usize {
    let (_, rows) = get_win_size();
    rows
}

fn get_win_size() -> (usize, usize) {
    use std::mem::zeroed;
    use libc::c_ushort;

    unsafe {
        #[repr(C)]
        struct winsize {
            ws_row: c_ushort,
            ws_col: c_ushort,
            ws_xpixel: c_ushort,
            ws_ypixel: c_ushort,
        }

        let mut size: winsize = zeroed();
        match libc::ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut size) {
            0 => (size.ws_col as usize, size.ws_row as usize), // TODO getCursorPosition
            _ => (80, 24),
        }
    }
}

/// Check TERM environment variable to see if current term is in our
/// unsupported list
fn is_unsupported_term() -> bool {
    use std::ascii::AsciiExt;
    match std::env::var("TERM") {
        Ok(term) => {
            let mut unsupported = false;
            for iter in &UNSUPPORTED_TERM {
                unsupported = (*iter).eq_ignore_ascii_case(&term)
            }
            unsupported
        }
        Err(_) => false,
    }
}

/// Return whether or not STDIN, STDOUT or STDERR is a TTY
fn is_a_tty(fd: libc::c_int) -> bool {
    unsafe { libc::isatty(fd) != 0 }
}

/// Enable RAW mode for the terminal.
pub fn enable_raw_mode() -> Result<Mode> {
    use nix::errno::Errno::ENOTTY;
    use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON,
                            /* OPOST, */ VMIN, VTIME};
    if !is_a_tty(STDIN_FILENO) {
        try!(Err(nix::Error::from_errno(ENOTTY)));
    }
    let original_mode = try!(termios::tcgetattr(STDIN_FILENO));
    let mut raw = original_mode;
    // disable BREAK interrupt, CR to NL conversion on input,
    // input parity check, strip high bit (bit 8), output flow control
    raw.c_iflag = raw.c_iflag & !(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    // we don't want raw output, it turns newlines into straight linefeeds
    // raw.c_oflag = raw.c_oflag & !(OPOST); // disable all output processing
    raw.c_cflag = raw.c_cflag | (CS8); // character-size mark (8 bits)
    // disable echoing, canonical mode, extended input processing and signals
    raw.c_lflag = raw.c_lflag & !(ECHO | ICANON | IEXTEN | ISIG);
    raw.c_cc[VMIN] = 1; // One character-at-a-time input
    raw.c_cc[VTIME] = 0; // with blocking read
    try!(termios::tcsetattr(STDIN_FILENO, termios::TCSAFLUSH, &raw));
    Ok(original_mode)
}

/// Disable RAW mode for the terminal.
pub fn disable_raw_mode(original_mode: Mode) -> Result<()> {
    try!(termios::tcsetattr(STDIN_FILENO, termios::TCSAFLUSH, &original_mode));
    Ok(())
}

fn clear_screen(w: &mut Write) -> Result<()> {
    try!(w.write_all(b"\x1b[H\x1b[2J"));
    try!(w.flush());
    Ok(())
}

/// Console input reader
pub struct PosixRawReader<R> {
    chars: Chars<R>,
}

impl<R: Read> PosixRawReader<R> {
    pub fn new(stdin: R) -> Result<PosixRawReader<R>> {
        Ok(PosixRawReader { chars: stdin.chars() })
    }

    fn escape_sequence(&mut self) -> Result<KeyPress> {
        // Read the next two bytes representing the escape sequence.
        let seq1 = try!(self.next_char());
        if seq1 == '[' {
            // ESC [ sequences.
            let seq2 = try!(self.next_char());
            if seq2.is_digit(10) {
                // Extended escape, read additional byte.
                let seq3 = try!(self.next_char());
                if seq3 == '~' {
                    match seq2 {
                        // '1' => Ok(KeyPress::Home),
                        '3' => Ok(KeyPress::Delete),
                        // '4' => Ok(KeyPress::End),
                        '5' => Ok(KeyPress::PageUp),
                        '6' => Ok(KeyPress::PageDown),
                        '7' => Ok(KeyPress::Home),
                        '8' => Ok(KeyPress::End),
                        _ => Ok(KeyPress::UnknownEscSeq),
                    }
                } else {
                    Ok(KeyPress::UnknownEscSeq)
                }
            } else {
                match seq2 {
                    'A' => Ok(KeyPress::Up), // ANSI
                    'B' => Ok(KeyPress::Down),
                    'C' => Ok(KeyPress::Right),
                    'D' => Ok(KeyPress::Left),
                    'F' => Ok(KeyPress::End),
                    'H' => Ok(KeyPress::Home),
                    _ => Ok(KeyPress::UnknownEscSeq),
                }
            }
        } else if seq1 == 'O' {
            // ESC O sequences.
            let seq2 = try!(self.next_char());
            match seq2 {
                'A' => Ok(KeyPress::Up),
                'B' => Ok(KeyPress::Down),
                'C' => Ok(KeyPress::Right),
                'D' => Ok(KeyPress::Left),
                'F' => Ok(KeyPress::End),
                'H' => Ok(KeyPress::Home),
                _ => Ok(KeyPress::UnknownEscSeq),
            }
        } else {
            // TODO ESC-N (n): search history forward not interactively
            // TODO ESC-P (p): search history backward not interactively
            // TODO ESC-R (r): Undo all changes made to this line.
            match seq1 {
                '\x08' => Ok(KeyPress::Meta('\x08')), // Backspace
                '<' => Ok(KeyPress::Meta('<')),
                '>' => Ok(KeyPress::Meta('>')),
                'b' | 'B' => Ok(KeyPress::Meta('B')),
                'c' | 'C' => Ok(KeyPress::Meta('C')),
                'd' | 'D' => Ok(KeyPress::Meta('D')),
                'f' | 'F' => Ok(KeyPress::Meta('F')),
                'l' | 'L' => Ok(KeyPress::Meta('L')),
                't' | 'T' => Ok(KeyPress::Meta('T')),
                'u' | 'U' => Ok(KeyPress::Meta('U')),
                'y' | 'Y' => Ok(KeyPress::Meta('Y')),
                '\x7f' => Ok(KeyPress::Meta('\x7f')), // Delete
                _ => {
                    // writeln!(io::stderr(), "key: {:?}, seq1: {:?}", KeyPress::Esc, seq1).unwrap();
                    Ok(KeyPress::UnknownEscSeq)
                }
            }
        }
    }
}

impl<R: Read> RawReader for PosixRawReader<R> {
    // As there is no read timeout to properly handle single ESC key,
    // we make possible to deactivate escape sequence processing.
    fn next_key(&mut self, esc_seq: bool) -> Result<KeyPress> {
        let c = try!(self.next_char());

        let mut key = consts::char_to_key_press(c);
        if esc_seq && key == KeyPress::Esc {
            // escape sequence
            key = try!(self.escape_sequence());
        }
        Ok(key)
    }

    fn next_char(&mut self) -> Result<char> {
        match self.chars.next() {
            Some(c) => Ok(try!(c)),
            None => Err(error::ReadlineError::Eof),
        }
    }
}

static SIGWINCH_ONCE: sync::Once = sync::ONCE_INIT;
static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT;

fn install_sigwinch_handler() {
    SIGWINCH_ONCE.call_once(|| unsafe {
        let sigwinch = signal::SigAction::new(signal::SigHandler::Handler(sigwinch_handler),
                                              signal::SaFlags::empty(),
                                              signal::SigSet::empty());
        let _ = signal::sigaction(signal::SIGWINCH, &sigwinch);
    });
}

extern "C" fn sigwinch_handler(_: libc::c_int) {
    SIGWINCH.store(true, atomic::Ordering::SeqCst);
}

pub type Terminal = PosixTerminal;

#[derive(Clone, Debug)]
pub struct PosixTerminal {
    unsupported: bool,
    stdin_isatty: bool,
}

impl PosixTerminal {
    pub fn new() -> PosixTerminal {
        let term = PosixTerminal {
            unsupported: is_unsupported_term(),
            stdin_isatty: is_a_tty(STDIN_FILENO),
        };
        if !term.unsupported && term.stdin_isatty && is_a_tty(STDOUT_FILENO) {
            install_sigwinch_handler();
        }
        term
    }

    // Init checks:

    /// Check if current terminal can provide a rich line-editing user interface.
    pub fn is_unsupported(&self) -> bool {
        self.unsupported
    }

    /// check if stdin is connected to a terminal.
    pub fn is_stdin_tty(&self) -> bool {
        self.stdin_isatty
    }

    // Interactive loop:

    /// Get the number of columns in the current terminal.
    pub fn get_columns(&self) -> usize {
        get_columns()
    }

    /// Get the number of rows in the current terminal.
    pub fn get_rows(&self) -> usize {
        get_rows()
    }

    /// Create a RAW reader
    pub fn create_reader(&self) -> Result<PosixRawReader<std::io::Stdin>> {
        PosixRawReader::new(std::io::stdin())
    }

    /// Check if a SIGWINCH signal has been received
    pub fn sigwinch(&self) -> bool {
        SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst)
    }

    /// Clear the screen. Used to handle ctrl+l
    pub fn clear_screen(&mut self, w: &mut Write) -> Result<()> {
        clear_screen(w)
    }
}
<commit_msg>Simplify PosixRawReader<commit_after>use std;
use std::io::{Chars, Read, Write};
use std::sync;
use std::sync::atomic;
use libc;
use nix;
use nix::sys::signal;
use nix::sys::termios;

use consts::{self, KeyPress};
use ::Result;
use ::error;
use super::RawReader;

pub type Mode = termios::Termios;
const STDIN_FILENO: libc::c_int = libc::STDIN_FILENO;
const STDOUT_FILENO: libc::c_int = libc::STDOUT_FILENO;

/// Unsupported Terminals that don't support RAW mode
static UNSUPPORTED_TERM: [&'static str; 3] = ["dumb", "cons25", "emacs"];

#[cfg(any(target_os = "macos", target_os = "freebsd"))]
const TIOCGWINSZ: libc::c_ulong = 0x40087468;

#[cfg(any(all(target_os = "linux", target_env = "gnu"), target_os = "android"))]
const TIOCGWINSZ: libc::c_ulong = 0x5413;

#[cfg(all(target_os = "linux", target_env = "musl"))]
const TIOCGWINSZ: libc::c_int = 0x5413;

/// Try to get the number of columns in the current terminal,
/// or assume 80 if it fails.
fn get_columns() -> usize {
    let (cols, _) = get_win_size();
    cols
}

/// Try to get the number of rows in the current terminal,
/// or assume 24 if it fails.
fn get_rows() -> usize {
    let (_, rows) = get_win_size();
    rows
}

fn get_win_size() -> (usize, usize) {
    use std::mem::zeroed;
    use libc::c_ushort;

    unsafe {
        #[repr(C)]
        struct winsize {
            ws_row: c_ushort,
            ws_col: c_ushort,
            ws_xpixel: c_ushort,
            ws_ypixel: c_ushort,
        }

        let mut size: winsize = zeroed();
        match libc::ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut size) {
            0 => (size.ws_col as usize, size.ws_row as usize), // TODO getCursorPosition
            _ => (80, 24),
        }
    }
}

/// Check TERM environment variable to see if current term is in our
/// unsupported list
fn is_unsupported_term() -> bool {
    use std::ascii::AsciiExt;
    match std::env::var("TERM") {
        Ok(term) => {
            let mut unsupported = false;
            for iter in &UNSUPPORTED_TERM {
                unsupported = (*iter).eq_ignore_ascii_case(&term)
            }
            unsupported
        }
        Err(_) => false,
    }
}

/// Return whether or not STDIN, STDOUT or STDERR is a TTY
fn is_a_tty(fd: libc::c_int) -> bool {
    unsafe { libc::isatty(fd) != 0 }
}

/// Enable RAW mode for the terminal.
pub fn enable_raw_mode() -> Result<Mode> {
    use nix::errno::Errno::ENOTTY;
    use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON,
                            /* OPOST, */ VMIN, VTIME};
    if !is_a_tty(STDIN_FILENO) {
        try!(Err(nix::Error::from_errno(ENOTTY)));
    }
    let original_mode = try!(termios::tcgetattr(STDIN_FILENO));
    let mut raw = original_mode;
    // disable BREAK interrupt, CR to NL conversion on input,
    // input parity check, strip high bit (bit 8), output flow control
    raw.c_iflag = raw.c_iflag & !(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    // we don't want raw output, it turns newlines into straight linefeeds
    // raw.c_oflag = raw.c_oflag & !(OPOST); // disable all output processing
    raw.c_cflag = raw.c_cflag | (CS8); // character-size mark (8 bits)
    // disable echoing, canonical mode, extended input processing and signals
    raw.c_lflag = raw.c_lflag & !(ECHO | ICANON | IEXTEN | ISIG);
    raw.c_cc[VMIN] = 1; // One character-at-a-time input
    raw.c_cc[VTIME] = 0; // with blocking read
    try!(termios::tcsetattr(STDIN_FILENO, termios::TCSAFLUSH, &raw));
    Ok(original_mode)
}

/// Disable RAW mode for the terminal.
pub fn disable_raw_mode(original_mode: Mode) -> Result<()> {
    try!(termios::tcsetattr(STDIN_FILENO, termios::TCSAFLUSH, &original_mode));
    Ok(())
}

fn clear_screen(w: &mut Write) -> Result<()> {
    try!(w.write_all(b"\x1b[H\x1b[2J"));
    try!(w.flush());
    Ok(())
}

/// Console input reader
pub struct PosixRawReader {
    chars: Chars<std::io::Stdin>,
}

impl PosixRawReader {
    pub fn new(stdin: std::io::Stdin) -> Result<PosixRawReader> {
        Ok(PosixRawReader { chars: stdin.chars() })
    }

    fn escape_sequence(&mut self) -> Result<KeyPress> {
        // Read the next two bytes representing the escape sequence.
        let seq1 = try!(self.next_char());
        if seq1 == '[' {
            // ESC [ sequences.
            let seq2 = try!(self.next_char());
            if seq2.is_digit(10) {
                // Extended escape, read additional byte.
                let seq3 = try!(self.next_char());
                if seq3 == '~' {
                    match seq2 {
                        // '1' => Ok(KeyPress::Home),
                        '3' => Ok(KeyPress::Delete),
                        // '4' => Ok(KeyPress::End),
                        '5' => Ok(KeyPress::PageUp),
                        '6' => Ok(KeyPress::PageDown),
                        '7' => Ok(KeyPress::Home),
                        '8' => Ok(KeyPress::End),
                        _ => Ok(KeyPress::UnknownEscSeq),
                    }
                } else {
                    Ok(KeyPress::UnknownEscSeq)
                }
            } else {
                match seq2 {
                    'A' => Ok(KeyPress::Up), // ANSI
                    'B' => Ok(KeyPress::Down),
                    'C' => Ok(KeyPress::Right),
                    'D' => Ok(KeyPress::Left),
                    'F' => Ok(KeyPress::End),
                    'H' => Ok(KeyPress::Home),
                    _ => Ok(KeyPress::UnknownEscSeq),
                }
            }
        } else if seq1 == 'O' {
            // ESC O sequences.
            let seq2 = try!(self.next_char());
            match seq2 {
                'A' => Ok(KeyPress::Up),
                'B' => Ok(KeyPress::Down),
                'C' => Ok(KeyPress::Right),
                'D' => Ok(KeyPress::Left),
                'F' => Ok(KeyPress::End),
                'H' => Ok(KeyPress::Home),
                _ => Ok(KeyPress::UnknownEscSeq),
            }
        } else {
            // TODO ESC-N (n): search history forward not interactively
            // TODO ESC-P (p): search history backward not interactively
            // TODO ESC-R (r): Undo all changes made to this line.
            match seq1 {
                '\x08' => Ok(KeyPress::Meta('\x08')), // Backspace
                '<' => Ok(KeyPress::Meta('<')),
                '>' => Ok(KeyPress::Meta('>')),
                'b' | 'B' => Ok(KeyPress::Meta('B')),
                'c' | 'C' => Ok(KeyPress::Meta('C')),
                'd' | 'D' => Ok(KeyPress::Meta('D')),
                'f' | 'F' => Ok(KeyPress::Meta('F')),
                'l' | 'L' => Ok(KeyPress::Meta('L')),
                't' | 'T' => Ok(KeyPress::Meta('T')),
                'u' | 'U' => Ok(KeyPress::Meta('U')),
                'y' | 'Y' => Ok(KeyPress::Meta('Y')),
                '\x7f' => Ok(KeyPress::Meta('\x7f')), // Delete
                _ => {
                    // writeln!(io::stderr(), "key: {:?}, seq1: {:?}", KeyPress::Esc, seq1).unwrap();
                    Ok(KeyPress::UnknownEscSeq)
                }
            }
        }
    }
}

impl RawReader for PosixRawReader {
    // As there is no read timeout to properly handle single ESC key,
    // we make possible to deactivate escape sequence processing.
    fn next_key(&mut self, esc_seq: bool) -> Result<KeyPress> {
        let c = try!(self.next_char());

        let mut key = consts::char_to_key_press(c);
        if esc_seq && key == KeyPress::Esc {
            // escape sequence
            key = try!(self.escape_sequence());
        }
        Ok(key)
    }

    fn next_char(&mut self) -> Result<char> {
        match self.chars.next() {
            Some(c) => Ok(try!(c)),
            None => Err(error::ReadlineError::Eof),
        }
    }
}

static SIGWINCH_ONCE: sync::Once = sync::ONCE_INIT;
static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT;

fn install_sigwinch_handler() {
    SIGWINCH_ONCE.call_once(|| unsafe {
        let sigwinch = signal::SigAction::new(signal::SigHandler::Handler(sigwinch_handler),
                                              signal::SaFlags::empty(),
                                              signal::SigSet::empty());
        let _ = signal::sigaction(signal::SIGWINCH, &sigwinch);
    });
}

extern "C" fn sigwinch_handler(_: libc::c_int) {
    SIGWINCH.store(true, atomic::Ordering::SeqCst);
}

pub type Terminal = PosixTerminal;

#[derive(Clone, Debug)]
pub struct PosixTerminal {
    unsupported: bool,
    stdin_isatty: bool,
}

impl PosixTerminal {
    pub fn new() -> PosixTerminal {
        let term = PosixTerminal {
            unsupported: is_unsupported_term(),
            stdin_isatty: is_a_tty(STDIN_FILENO),
        };
        if !term.unsupported && term.stdin_isatty && is_a_tty(STDOUT_FILENO) {
            install_sigwinch_handler();
        }
        term
    }

    // Init checks:

    /// Check if current terminal can provide a rich line-editing user interface.
    pub fn is_unsupported(&self) -> bool {
        self.unsupported
    }

    /// check if stdin is connected to a terminal.
    pub fn is_stdin_tty(&self) -> bool {
        self.stdin_isatty
    }

    // Interactive loop:

    /// Get the number of columns in the current terminal.
    pub fn get_columns(&self) -> usize {
        get_columns()
    }

    /// Get the number of rows in the current terminal.
    pub fn get_rows(&self) -> usize {
        get_rows()
    }

    /// Create a RAW reader
    pub fn create_reader(&self) -> Result<PosixRawReader> {
        PosixRawReader::new(std::io::stdin())
    }

    /// Check if a SIGWINCH signal has been received
    pub fn sigwinch(&self) -> bool {
        SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst)
    }

    /// Clear the screen. Used to handle ctrl+l
    pub fn clear_screen(&mut self, w: &mut Write) -> Result<()> {
        clear_screen(w)
    }
}
<|endoftext|>


<commit_before>use core::position::{Size, HasSize};
use core::cellbuffer::CellAccessor;

use ui::core::{
    Alignable,
    HorizontalAlign,
    VerticalAlign,
    Widget,
    Frame,
    Painter
};

/// Display text to widgets
///
/// # Examples
///
/// ```
/// use rustty::ui::core::{VerticalAlign, HorizontalAlign, Widget};
/// use rustty::ui::{Dialog, Label};
///
/// let mut maindlg = Dialog::new(60, 10);
///
/// let mut label = Label::from_str("Hi, this is an example!");
/// label.pack(&maindlg, HorizontalAlign::Middle, VerticalAlign::Middle, (0,0));
/// label.draw(maindlg.frame_mut());
///
/// maindlg.draw_box();
/// ```
///
pub struct Label {
    frame: Frame,
    text: String,
    x: usize,
    y: usize,
    t_halign: HorizontalAlign,
    t_valign: VerticalAlign,
    t_margin: (usize, usize)
}

impl Label {
    /// Construct a new Label widget `cols` wide by `rols` high. Initial text is empty
    /// and left aligned
    ///
    /// # Examples
    ///
    /// ```
    /// use rustty::ui::Label;
    ///
    /// let mut label = Label::new(60, 10);
    /// ```
    ///
    pub fn new(cols: usize, rows: usize) -> Label {
        Label {
            frame: Frame::new(cols, rows),
            text: "".to_string(),
            x: 0,
            y: 0,
            t_halign: HorizontalAlign::Left,
            t_valign: VerticalAlign::Middle,
            t_margin: (0, 0)
        }
    }

    /// Construct a new label widget from an existing string *s*. *s* can either be a
    /// `&str` or `String` , and a label will be constructed that is the size of the
    /// length of characters in *s*. Text is left aligned by default
    ///
    /// # Examples
    ///
    /// ```
    /// use rustty::ui::Label;
    ///
    /// let mut label1 = Label::from_str("This is a label");    // label is size (15x1)
    ///
    /// let s = "Here's another label".to_string();
    /// let mut label2 = Label::from_str(s);                    // label is size (20x1)
    /// ```
    ///
    pub fn from_str<S: Into<String>>(s: S) -> Label {
        let s = s.into();
        Label {
            frame: Frame::new(s.len(), 1),
            text: s.into(),
            x: 0,
            y: 0,
            t_halign: HorizontalAlign::Left,
            t_valign: VerticalAlign::Middle,
            t_margin: (1, 1)
        }
    }

    /// Specify a custom alignment for the text within the widget
    ///
    /// # Examples
    ///
    /// ```
    /// use rustty::ui::core::{HorizontalAlign, VerticalAlign};
    /// use rustty::ui::Label;
    ///
    /// let mut label = Label::new(20, 3);
    /// label.set_text("Centered");
    /// label.align_text(HorizontalAlign::Middle, VerticalAlign::Middle, (0,0));
    /// ```
    ///
    pub fn align_text(&mut self, halign: HorizontalAlign, valign: VerticalAlign,
                    margin: (usize, usize)) {
        self.t_halign = halign.clone();
        self.t_valign = valign.clone();
        self.t_margin = margin;

        self.x = self.frame.halign_line(&self.text, halign, margin.0);
        self.y = self.frame.valign_line(&self.text, valign, margin.1);
    }

    /// Set the text of the widget to the passed `&str` or `String`. If the
    /// widget does not have enough room to display the new text, the widget
    /// is resized **horizontally** to accomodate the new size
    ///
    /// # Examples
    ///
    /// ```
    /// use rustty::ui::Label;
    ///
    /// let mut label1 = Label::new(20, 3);
    /// label1.set_text("Initial text");
    ///
    /// let mut label2 = Label::from_str("too small");  // label is size (9x1)
    /// label2.set_text("This is too big");             // label is size (15x1)
    ///
    /// let mut label3 = Label::new(4, 2);              // label is size (4x2)
    /// label3.set_text("Too big to fit!");             // label is size (7x2)
    /// ```
    ///
    pub fn set_text<S: Into<String>>(&mut self, new_str: S) {
        let new_str = new_str.into();
        self.text = new_str;
        let (framex, framey) = self.frame.size();
        if self.text.len() > (framex * framey) {
            // Extend widget horizontally such that it can accomodate the new
            // text size, algorithm to determine new horizontal size is:
            //      L   : length of new text
            //      CxR : dimensions of widget
            //      ciel(L - C * R) / R + C
            let new_x = (self.text.len() - framex * framey) as f32 / framey as f32;
            let new_x = new_x.ceil() as usize + framex;
            self.frame.resize((new_x, framey));
        }
    }
}

impl Widget for Label {
    fn draw(&mut self, parent: &mut CellAccessor) {
        self.frame.printline(self.x, self.y, &self.text);
        self.frame.draw_into(parent);
    }

    fn pack(&mut self, parent: &HasSize, halign: HorizontalAlign, valign: VerticalAlign,
                margin: (usize, usize)) {
        self.frame.align(parent, halign, valign, margin);
        self.x = self.frame.halign_line(&self.text, self.t_halign.clone(), margin.0);
        self.y = self.frame.valign_line(&self.text, self.t_valign.clone(), margin.1);
    }

    fn draw_box(&mut self) {
        self.frame.draw_box();
    }

    fn resize(&mut self, new_size: Size) {
        self.frame.resize(new_size);
    }

    fn frame(&self) -> &Frame {
        &self.frame
    }

    fn frame_mut(&mut self) -> &mut Frame {
        &mut self.frame
    }
}
<commit_msg>Add asserts to label examples<commit_after>use core::position::{Size, HasSize};
use core::cellbuffer::CellAccessor;

use ui::core::{
    Alignable,
    HorizontalAlign,
    VerticalAlign,
    Widget,
    Frame,
    Painter
};

/// Display text to widgets
///
/// # Examples
///
/// ```
/// use rustty::ui::core::{VerticalAlign, HorizontalAlign, Widget};
/// use rustty::ui::{Dialog, Label};
///
/// let mut maindlg = Dialog::new(60, 10);
///
/// let mut label = Label::from_str("Hi, this is an example!");
/// label.pack(&maindlg, HorizontalAlign::Middle, VerticalAlign::Middle, (0,0));
/// label.draw(maindlg.frame_mut());
///
/// maindlg.draw_box();
/// ```
///
pub struct Label {
    frame: Frame,
    text: String,
    x: usize,
    y: usize,
    t_halign: HorizontalAlign,
    t_valign: VerticalAlign,
    t_margin: (usize, usize)
}

impl Label {
    /// Construct a new Label widget `cols` wide by `rols` high. Initial text is empty
    /// and left aligned
    ///
    /// # Examples
    ///
    /// ```
    /// use rustty::ui::Label;
    ///
    /// let mut label = Label::new(60, 10);
    /// ```
    ///
    pub fn new(cols: usize, rows: usize) -> Label {
        Label {
            frame: Frame::new(cols, rows),
            text: "".to_string(),
            x: 0,
            y: 0,
            t_halign: HorizontalAlign::Left,
            t_valign: VerticalAlign::Middle,
            t_margin: (0, 0)
        }
    }

    /// Construct a new label widget from an existing string *s*. *s* can either be a
    /// `&str` or `String` , and a label will be constructed that is the size of the
    /// length of characters in *s*. Text is left aligned by default
    ///
    /// # Examples
    ///
    /// ```
    /// use rustty::ui::Label;
    ///
    /// let mut label1 = Label::from_str("This is a label");    // label is size (15x1)
    ///
    /// let s = "Here's another label".to_string();
    /// let mut label2 = Label::from_str(s);                    // label is size (20x1)
    /// ```
    ///
    pub fn from_str<S: Into<String>>(s: S) -> Label {
        let s = s.into();
        Label {
            frame: Frame::new(s.len(), 1),
            text: s.into(),
            x: 0,
            y: 0,
            t_halign: HorizontalAlign::Left,
            t_valign: VerticalAlign::Middle,
            t_margin: (1, 1)
        }
    }

    /// Specify a custom alignment for the text within the widget
    ///
    /// # Examples
    ///
    /// ```
    /// use rustty::ui::core::{HorizontalAlign, VerticalAlign};
    /// use rustty::ui::Label;
    ///
    /// let mut label = Label::new(20, 3);
    /// label.set_text("Centered");
    /// label.align_text(HorizontalAlign::Middle, VerticalAlign::Middle, (0,0));
    /// ```
    ///
    pub fn align_text(&mut self, halign: HorizontalAlign, valign: VerticalAlign,
                    margin: (usize, usize)) {
        self.t_halign = halign.clone();
        self.t_valign = valign.clone();
        self.t_margin = margin;

        self.x = self.frame.halign_line(&self.text, halign, margin.0);
        self.y = self.frame.valign_line(&self.text, valign, margin.1);
    }

    /// Set the text of the widget to the passed `&str` or `String`. If the
    /// widget does not have enough room to display the new text, the widget
    /// is resized **horizontally** to accomodate the new size
    ///
    /// # Examples
    ///
    /// ```
    /// use rustty::HasSize;
    /// use rustty::ui::core::Widget;
    /// use rustty::ui::Label;
    ///
    /// let mut label1 = Label::new(20, 3);
    /// label1.set_text("Initial text");
    ///
    /// let mut label2 = Label::from_str("too small");  // label is size (9x1)
    /// label2.set_text("This is too big");             // label is size (15x1)
    /// assert_eq!(label2.frame().size(), (15, 1));
    ///
    /// let mut label3 = Label::new(4, 2);              // label is size (4x2)
    /// label3.set_text("Too big to fit!");             // label is size (8x2)
    /// assert_eq!(label3.frame().size(), (8, 2));
    /// ```
    ///
    pub fn set_text<S: Into<String>>(&mut self, new_str: S) {
        let new_str = new_str.into();
        self.text = new_str;
        let (framex, framey) = self.frame.size();
        if self.text.len() > (framex * framey) {
            // Extend widget horizontally such that it can accomodate the new
            // text size, algorithm to determine new horizontal size is:
            //      L   : length of new text
            //      CxR : dimensions of widget
            //      ciel(L - C * R) / R + C
            let new_x = (self.text.len() - framex * framey) as f32 / framey as f32;
            let new_x = new_x.ceil() as usize + framex;
            self.frame.resize((new_x, framey));
        }
    }
}

impl Widget for Label {
    fn draw(&mut self, parent: &mut CellAccessor) {
        self.frame.printline(self.x, self.y, &self.text);
        self.frame.draw_into(parent);
    }

    fn pack(&mut self, parent: &HasSize, halign: HorizontalAlign, valign: VerticalAlign,
                margin: (usize, usize)) {
        self.frame.align(parent, halign, valign, margin);
        self.x = self.frame.halign_line(&self.text, self.t_halign.clone(), margin.0);
        self.y = self.frame.valign_line(&self.text, self.t_valign.clone(), margin.1);
    }

    fn draw_box(&mut self) {
        self.frame.draw_box();
    }

    fn resize(&mut self, new_size: Size) {
        self.frame.resize(new_size);
    }

    fn frame(&self) -> &Frame {
        &self.frame
    }

    fn frame_mut(&mut self) -> &mut Frame {
        &mut self.frame
    }
}
<|endoftext|>


<commit_before>#![allow(non_camel_case_types, non_snake_case)]
extern crate ncurses;

use ncurses::ll::{chtype, attr_t, WINDOW, wattrset};

use ncurses::NCURSES_ATTR_SHIFT;

pub fn NCURSES_BITS(mask: u32, shift: u32) -> u32 {
    mask << (shift + NCURSES_ATTR_SHIFT) as usize
}

pub const COLOR_BLACK: i16 = 0;
pub const COLOR_RED: i16 = 1;
pub const COLOR_GREEN: i16 = 2;
pub const COLOR_YELLOW: i16 = 3;
pub const COLOR_BLUE: i16 = 4;
pub const COLOR_MAGENTA: i16 = 5;
pub const COLOR_CYAN: i16 = 6;
pub const COLOR_WHITE: i16 = 7;

pub const A_NORMAL: attr_t = 0u32 as attr_t;
pub const A_ATTRIBUTES: attr_t = (!0u32 << (0u32 + NCURSES_ATTR_SHIFT)) as attr_t;
pub const A_CHARTEXT: attr_t = (1u32 << (0u32 + NCURSES_ATTR_SHIFT)) as attr_t;
pub const A_COLOR: attr_t = ((((1u32) << 8) - 1u32) << (0u32 + NCURSES_ATTR_SHIFT)) as attr_t;
pub const A_BLINK: attr_t = (1u32 << (11u32 + NCURSES_ATTR_SHIFT)) as attr_t;

// pub fn A_STANDOUT() -> attr_t
// { NCURSES_BITS(1u32, 8u32) as attr_t }
//
// pub fn A_UNDERLINE() -> attr_t
// { NCURSES_BITS(1u32, 9u32) as attr_t }
//
// pub fn A_REVERSE() -> attr_t
// { NCURSES_BITS(1u32, 10u32) as attr_t }
//
// pub fn A_DIM() -> attr_t
// { NCURSES_BITS(1u32, 12u32) as attr_t }
//

pub const A_BOLD: attr_t = (1u32 << (13u32 + NCURSES_ATTR_SHIFT)) as attr_t;

pub const KEY_RESIZE: i32 = 0632;

pub fn COLOR_PAIR(n: chtype) -> attr_t {
    NCURSES_BITS(n as u32, 0u32) as attr_t
}

pub fn _attrset(w: WINDOW, attributes: chtype) -> i32 {
    unsafe { wattrset(w, attributes as i32) }
}

pub fn _draw_box(w: *mut WINDOW, verch: chtype, horch: chtype) -> i32 {
    unsafe { box_(w, verch, horch) }
}

pub fn _resize_term(_nlines: i32, _ncols: i32) -> i32 {
    error!("resize_term is not implemented in ncurses-rs");
    -1
}

use input::Input;

const KEY_OFFSET: i32 = 0o0400;
const KEY_F15: i32 = (KEY_OFFSET + 0x17);
const KEY_EVENT: i32 = (KEY_OFFSET + 0o633);

const SPECIAL_KEY_CODES: [Input; 108] = [Input::KeyCodeYes,

                                         Input::KeyBreak,
                                         Input::KeyDown,
                                         Input::KeyUp,
                                         Input::KeyLeft,
                                         Input::KeyRight,
                                         Input::KeyHome,
                                         Input::KeyBackspace,

                                         Input::KeyF0,
                                         Input::KeyF1,
                                         Input::KeyF2,
                                         Input::KeyF3,
                                         Input::KeyF4,
                                         Input::KeyF5,
                                         Input::KeyF6,
                                         Input::KeyF7,
                                         Input::KeyF8,
                                         Input::KeyF9,
                                         Input::KeyF10,
                                         Input::KeyF11,
                                         Input::KeyF12,
                                         Input::KeyF13,
                                         Input::KeyF14,
                                         Input::KeyF15,
                                         // ncurses reserves space for 64 function keys, but we've
                                         // only implemented 15. This has to be taken into account
                                         // when converting the integer into an index of this array
                                         Input::KeyDL,
                                         Input::KeyIL,
                                         Input::KeyDC,
                                         Input::KeyIC,
                                         Input::KeyEIC,
                                         Input::KeyClear,
                                         Input::KeyEOS,
                                         Input::KeyEOL,
                                         Input::KeySF,
                                         Input::KeySR,
                                         Input::KeyNPage,
                                         Input::KeyPPage,
                                         Input::KeySTab,
                                         Input::KeyCTab,
                                         Input::KeyCATab,
                                         Input::KeyEnter,
                                         Input::KeySReset,
                                         Input::KeyReset,
                                         Input::KeyPrint,
                                         Input::KeyLL,
                                         Input::KeyA1,
                                         Input::KeyA3,
                                         Input::KeyB2,
                                         Input::KeyC1,
                                         Input::KeyC3,
                                         Input::KeyBTab,
                                         Input::KeyBeg,
                                         Input::KeyCancel,
                                         Input::KeyClose,
                                         Input::KeyCommand,
                                         Input::KeyCopy,
                                         Input::KeyCreate,
                                         Input::KeyEnd,
                                         Input::KeyExit,
                                         Input::KeyFind,
                                         Input::KeyHelp,
                                         Input::KeyMark,
                                         Input::KeyMessage,
                                         Input::KeyMove,
                                         Input::KeyNext,
                                         Input::KeyOpen,
                                         Input::KeyOptions,
                                         Input::KeyPrevious,
                                         Input::KeyRedo,
                                         Input::KeyReference,
                                         Input::KeyRefresh,
                                         Input::KeyReplace,
                                         Input::KeyRestart,
                                         Input::KeyResume,
                                         Input::KeySave,
                                         Input::KeySBeg,
                                         Input::KeySCancel,
                                         Input::KeySCommand,
                                         Input::KeySCopy,
                                         Input::KeySCreate,
                                         Input::KeySDC,
                                         Input::KeySDL,
                                         Input::KeySelect,
                                         Input::KeySEnd,
                                         Input::KeySEOL,
                                         Input::KeySExit,
                                         Input::KeySFind,
                                         Input::KeySHelp,
                                         Input::KeySHome,
                                         Input::KeySIC,

                                         Input::KeySLeft,
                                         Input::KeySMessage,
                                         Input::KeySMove,
                                         Input::KeySNext,
                                         Input::KeySOptions,
                                         Input::KeySPrevious,
                                         Input::KeySPrint,
                                         Input::KeySRedo,
                                         Input::KeySReplace,
                                         Input::KeySRight,
                                         Input::KeySResume,
                                         Input::KeySSave,
                                         Input::KeySSuspend,
                                         Input::KeySUndo,
                                         Input::KeySuspend,
                                         Input::KeyUndo,
                                         Input::KeyMouse,
                                         Input::KeyResize,
                                         Input::KeyEvent,
];

/// Converts an integer returned by getch() to a Input value
pub fn to_special_keycode(i: i32) -> Input {
    assert!(i >= KEY_OFFSET && i <= KEY_EVENT);
    let i = if i <= KEY_F15 {
        i - KEY_OFFSET
    } else {
        i - KEY_OFFSET - 48
    };
    SPECIAL_KEY_CODES[i as usize]
}

#[cfg(test)]
mod tests {
    use super::*;
    use input::Input;

    #[test]
    fn test_key_dl_to_special_keycode() {
        let keyDl = 0o510;
        assert_eq!(Input::KeyDL, to_special_keycode(keyDl));
    }

    #[test]
    fn test_key_f15_to_input() {
        let keyF15 = 0o410 + 15;
        assert_eq!(Input::KeyF15, to_special_keycode(keyF15));
    }

    #[test]
    fn test_key_up_to_input() {
        let keyUp = 0o403;
        assert_eq!(Input::KeyUp, to_special_keycode(keyUp));
    }
}
<commit_msg>Fixed build<commit_after>#![allow(non_camel_case_types, non_snake_case)]
extern crate ncurses;

use ncurses::ll::{box_, chtype, attr_t, WINDOW, wattrset};

use ncurses::NCURSES_ATTR_SHIFT;

pub fn NCURSES_BITS(mask: u32, shift: u32) -> u32 {
    mask << (shift + NCURSES_ATTR_SHIFT) as usize
}

pub const COLOR_BLACK: i16 = 0;
pub const COLOR_RED: i16 = 1;
pub const COLOR_GREEN: i16 = 2;
pub const COLOR_YELLOW: i16 = 3;
pub const COLOR_BLUE: i16 = 4;
pub const COLOR_MAGENTA: i16 = 5;
pub const COLOR_CYAN: i16 = 6;
pub const COLOR_WHITE: i16 = 7;

pub const A_NORMAL: attr_t = 0u32 as attr_t;
pub const A_ATTRIBUTES: attr_t = (!0u32 << (0u32 + NCURSES_ATTR_SHIFT)) as attr_t;
pub const A_CHARTEXT: attr_t = (1u32 << (0u32 + NCURSES_ATTR_SHIFT)) as attr_t;
pub const A_COLOR: attr_t = ((((1u32) << 8) - 1u32) << (0u32 + NCURSES_ATTR_SHIFT)) as attr_t;
pub const A_BLINK: attr_t = (1u32 << (11u32 + NCURSES_ATTR_SHIFT)) as attr_t;

// pub fn A_STANDOUT() -> attr_t
// { NCURSES_BITS(1u32, 8u32) as attr_t }
//
// pub fn A_UNDERLINE() -> attr_t
// { NCURSES_BITS(1u32, 9u32) as attr_t }
//
// pub fn A_REVERSE() -> attr_t
// { NCURSES_BITS(1u32, 10u32) as attr_t }
//
// pub fn A_DIM() -> attr_t
// { NCURSES_BITS(1u32, 12u32) as attr_t }
//

pub const A_BOLD: attr_t = (1u32 << (13u32 + NCURSES_ATTR_SHIFT)) as attr_t;

pub const KEY_RESIZE: i32 = 0632;

pub fn COLOR_PAIR(n: chtype) -> attr_t {
    NCURSES_BITS(n as u32, 0u32) as attr_t
}

pub fn _attrset(w: WINDOW, attributes: chtype) -> i32 {
    unsafe { wattrset(w, attributes as i32) }
}

pub fn _draw_box(w: WINDOW, verch: chtype, horch: chtype) -> i32 {
    unsafe { box_(w, verch, horch) }
}

pub fn _resize_term(_nlines: i32, _ncols: i32) -> i32 {
    error!("resize_term is not implemented in ncurses-rs");
    -1
}

use input::Input;

const KEY_OFFSET: i32 = 0o0400;
const KEY_F15: i32 = (KEY_OFFSET + 0x17);
const KEY_EVENT: i32 = (KEY_OFFSET + 0o633);

const SPECIAL_KEY_CODES: [Input; 108] = [Input::KeyCodeYes,

                                         Input::KeyBreak,
                                         Input::KeyDown,
                                         Input::KeyUp,
                                         Input::KeyLeft,
                                         Input::KeyRight,
                                         Input::KeyHome,
                                         Input::KeyBackspace,

                                         Input::KeyF0,
                                         Input::KeyF1,
                                         Input::KeyF2,
                                         Input::KeyF3,
                                         Input::KeyF4,
                                         Input::KeyF5,
                                         Input::KeyF6,
                                         Input::KeyF7,
                                         Input::KeyF8,
                                         Input::KeyF9,
                                         Input::KeyF10,
                                         Input::KeyF11,
                                         Input::KeyF12,
                                         Input::KeyF13,
                                         Input::KeyF14,
                                         Input::KeyF15,
                                         // ncurses reserves space for 64 function keys, but we've
                                         // only implemented 15. This has to be taken into account
                                         // when converting the integer into an index of this array
                                         Input::KeyDL,
                                         Input::KeyIL,
                                         Input::KeyDC,
                                         Input::KeyIC,
                                         Input::KeyEIC,
                                         Input::KeyClear,
                                         Input::KeyEOS,
                                         Input::KeyEOL,
                                         Input::KeySF,
                                         Input::KeySR,
                                         Input::KeyNPage,
                                         Input::KeyPPage,
                                         Input::KeySTab,
                                         Input::KeyCTab,
                                         Input::KeyCATab,
                                         Input::KeyEnter,
                                         Input::KeySReset,
                                         Input::KeyReset,
                                         Input::KeyPrint,
                                         Input::KeyLL,
                                         Input::KeyA1,
                                         Input::KeyA3,
                                         Input::KeyB2,
                                         Input::KeyC1,
                                         Input::KeyC3,
                                         Input::KeyBTab,
                                         Input::KeyBeg,
                                         Input::KeyCancel,
                                         Input::KeyClose,
                                         Input::KeyCommand,
                                         Input::KeyCopy,
                                         Input::KeyCreate,
                                         Input::KeyEnd,
                                         Input::KeyExit,
                                         Input::KeyFind,
                                         Input::KeyHelp,
                                         Input::KeyMark,
                                         Input::KeyMessage,
                                         Input::KeyMove,
                                         Input::KeyNext,
                                         Input::KeyOpen,
                                         Input::KeyOptions,
                                         Input::KeyPrevious,
                                         Input::KeyRedo,
                                         Input::KeyReference,
                                         Input::KeyRefresh,
                                         Input::KeyReplace,
                                         Input::KeyRestart,
                                         Input::KeyResume,
                                         Input::KeySave,
                                         Input::KeySBeg,
                                         Input::KeySCancel,
                                         Input::KeySCommand,
                                         Input::KeySCopy,
                                         Input::KeySCreate,
                                         Input::KeySDC,
                                         Input::KeySDL,
                                         Input::KeySelect,
                                         Input::KeySEnd,
                                         Input::KeySEOL,
                                         Input::KeySExit,
                                         Input::KeySFind,
                                         Input::KeySHelp,
                                         Input::KeySHome,
                                         Input::KeySIC,

                                         Input::KeySLeft,
                                         Input::KeySMessage,
                                         Input::KeySMove,
                                         Input::KeySNext,
                                         Input::KeySOptions,
                                         Input::KeySPrevious,
                                         Input::KeySPrint,
                                         Input::KeySRedo,
                                         Input::KeySReplace,
                                         Input::KeySRight,
                                         Input::KeySResume,
                                         Input::KeySSave,
                                         Input::KeySSuspend,
                                         Input::KeySUndo,
                                         Input::KeySuspend,
                                         Input::KeyUndo,
                                         Input::KeyMouse,
                                         Input::KeyResize,
                                         Input::KeyEvent,
];

/// Converts an integer returned by getch() to a Input value
pub fn to_special_keycode(i: i32) -> Input {
    assert!(i >= KEY_OFFSET && i <= KEY_EVENT);
    let i = if i <= KEY_F15 {
        i - KEY_OFFSET
    } else {
        i - KEY_OFFSET - 48
    };
    SPECIAL_KEY_CODES[i as usize]
}

#[cfg(test)]
mod tests {
    use super::*;
    use input::Input;

    #[test]
    fn test_key_dl_to_special_keycode() {
        let keyDl = 0o510;
        assert_eq!(Input::KeyDL, to_special_keycode(keyDl));
    }

    #[test]
    fn test_key_f15_to_input() {
        let keyF15 = 0o410 + 15;
        assert_eq!(Input::KeyF15, to_special_keycode(keyF15));
    }

    #[test]
    fn test_key_up_to_input() {
        let keyUp = 0o403;
        assert_eq!(Input::KeyUp, to_special_keycode(keyUp));
    }
}
<|endoftext|>