<commit_before>use crate::{
    SyntaxKind::{self, *},
    TextUnit,
};

/// A token of Rust source.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Token {
    /// The kind of token.
    pub kind: SyntaxKind,
    /// The length of the token.
    pub len: TextUnit,
}

/// Break a string up into its component tokens
pub fn tokenize(text: &str) -> Vec<Token> {
    if text.is_empty() {
        return vec![];
    }
    let mut text = text;
    let mut acc = Vec::new();
    if let Some(len) = ra_rustc_lexer::strip_shebang(text) {
        acc.push(Token { kind: SHEBANG, len: TextUnit::from_usize(len) });
        text = &text[len..];
    }
    while !text.is_empty() {
        let rustc_token = ra_rustc_lexer::first_token(text);
        macro_rules! decompose {
            ($t1:expr, $t2:expr) => {{
                acc.push(Token { kind: $t1, len: 1.into() });
                acc.push(Token { kind: $t2, len: 1.into() });
                text = &text[2..];
                continue;
            }};
            ($t1:expr, $t2:expr, $t3:expr) => {{
                acc.push(Token { kind: $t1, len: 1.into() });
                acc.push(Token { kind: $t2, len: 1.into() });
                acc.push(Token { kind: $t3, len: 1.into() });
                text = &text[3..];
                continue;
            }};
        }
        let kind = match rustc_token.kind {
            ra_rustc_lexer::TokenKind::LineComment => COMMENT,
            ra_rustc_lexer::TokenKind::BlockComment { .. } => COMMENT,
            ra_rustc_lexer::TokenKind::Whitespace => WHITESPACE,
            ra_rustc_lexer::TokenKind::Ident => {
                let token_text = &text[..rustc_token.len];
                if token_text == "_" {
                    UNDERSCORE
                } else {
                    SyntaxKind::from_keyword(&text[..rustc_token.len]).unwrap_or(IDENT)
                }
            }
            ra_rustc_lexer::TokenKind::RawIdent => IDENT,
            ra_rustc_lexer::TokenKind::Literal { kind, .. } => match kind {
                ra_rustc_lexer::LiteralKind::Int { .. } => INT_NUMBER,
                ra_rustc_lexer::LiteralKind::Float { .. } => FLOAT_NUMBER,
                ra_rustc_lexer::LiteralKind::Char { .. } => CHAR,
                ra_rustc_lexer::LiteralKind::Byte { .. } => BYTE,
                ra_rustc_lexer::LiteralKind::Str { .. } => STRING,
                ra_rustc_lexer::LiteralKind::ByteStr { .. } => BYTE_STRING,
                ra_rustc_lexer::LiteralKind::RawStr { .. } => RAW_STRING,
                ra_rustc_lexer::LiteralKind::RawByteStr { .. } => RAW_BYTE_STRING,
            },
            ra_rustc_lexer::TokenKind::Lifetime { .. } => LIFETIME,
            ra_rustc_lexer::TokenKind::Semi => SEMI,
            ra_rustc_lexer::TokenKind::Comma => COMMA,
            ra_rustc_lexer::TokenKind::DotDotDot => decompose!(DOT, DOT, DOT),
            ra_rustc_lexer::TokenKind::DotDotEq => decompose!(DOT, DOT, EQ),
            ra_rustc_lexer::TokenKind::DotDot => decompose!(DOT, DOT),
            ra_rustc_lexer::TokenKind::Dot => DOT,
            ra_rustc_lexer::TokenKind::OpenParen => L_PAREN,
            ra_rustc_lexer::TokenKind::CloseParen => R_PAREN,
            ra_rustc_lexer::TokenKind::OpenBrace => L_CURLY,
            ra_rustc_lexer::TokenKind::CloseBrace => R_CURLY,
            ra_rustc_lexer::TokenKind::OpenBracket => L_BRACK,
            ra_rustc_lexer::TokenKind::CloseBracket => R_BRACK,
            ra_rustc_lexer::TokenKind::At => AT,
            ra_rustc_lexer::TokenKind::Pound => POUND,
            ra_rustc_lexer::TokenKind::Tilde => TILDE,
            ra_rustc_lexer::TokenKind::Question => QUESTION,
            ra_rustc_lexer::TokenKind::ColonColon => decompose!(COLON, COLON),
            ra_rustc_lexer::TokenKind::Colon => COLON,
            ra_rustc_lexer::TokenKind::Dollar => DOLLAR,
            ra_rustc_lexer::TokenKind::EqEq => decompose!(EQ, EQ),
            ra_rustc_lexer::TokenKind::Eq => EQ,
            ra_rustc_lexer::TokenKind::FatArrow => decompose!(EQ, R_ANGLE),
            ra_rustc_lexer::TokenKind::Ne => decompose!(EXCL, EQ),
            ra_rustc_lexer::TokenKind::Not => EXCL,
            ra_rustc_lexer::TokenKind::Le => decompose!(L_ANGLE, EQ),
            ra_rustc_lexer::TokenKind::LArrow => decompose!(COLON, MINUS),
            ra_rustc_lexer::TokenKind::Lt => L_ANGLE,
            ra_rustc_lexer::TokenKind::ShlEq => decompose!(L_ANGLE, L_ANGLE, EQ),
            ra_rustc_lexer::TokenKind::Shl => decompose!(L_ANGLE, L_ANGLE),
            ra_rustc_lexer::TokenKind::Ge => decompose!(R_ANGLE, EQ),
            ra_rustc_lexer::TokenKind::Gt => R_ANGLE,
            ra_rustc_lexer::TokenKind::ShrEq => decompose!(R_ANGLE, R_ANGLE, EQ),
            ra_rustc_lexer::TokenKind::Shr => decompose!(R_ANGLE, R_ANGLE),
            ra_rustc_lexer::TokenKind::RArrow => decompose!(MINUS, R_ANGLE),
            ra_rustc_lexer::TokenKind::Minus => MINUS,
            ra_rustc_lexer::TokenKind::MinusEq => decompose!(MINUS, EQ),
            ra_rustc_lexer::TokenKind::And => AMP,
            ra_rustc_lexer::TokenKind::AndAnd => decompose!(AMP, AMP),
            ra_rustc_lexer::TokenKind::AndEq => decompose!(AMP, EQ),
            ra_rustc_lexer::TokenKind::Or => PIPE,
            ra_rustc_lexer::TokenKind::OrOr => decompose!(PIPE, PIPE),
            ra_rustc_lexer::TokenKind::OrEq => decompose!(PIPE, EQ),
            ra_rustc_lexer::TokenKind::PlusEq => decompose!(PLUS, EQ),
            ra_rustc_lexer::TokenKind::Plus => PLUS,
            ra_rustc_lexer::TokenKind::StarEq => decompose!(STAR, EQ),
            ra_rustc_lexer::TokenKind::Star => STAR,
            ra_rustc_lexer::TokenKind::SlashEq => decompose!(SLASH, EQ),
            ra_rustc_lexer::TokenKind::Slash => SLASH,
            ra_rustc_lexer::TokenKind::CaretEq => decompose!(CARET, EQ),
            ra_rustc_lexer::TokenKind::Caret => CARET,
            ra_rustc_lexer::TokenKind::PercentEq => decompose!(PERCENT, EQ),
            ra_rustc_lexer::TokenKind::Percent => PERCENT,
            ra_rustc_lexer::TokenKind::Unknown => ERROR,
        };
        let token = Token { kind, len: TextUnit::from_usize(rustc_token.len) };
        acc.push(token);
        text = &text[rustc_token.len..];
    }
    acc
}

pub fn classify_literal(text: &str) -> Option<Token> {
    let t = ra_rustc_lexer::first_token(text);
    if t.len != text.len() {
        return None;
    }
    let kind = match t.kind {
        ra_rustc_lexer::TokenKind::Literal { kind, .. } => match kind {
            ra_rustc_lexer::LiteralKind::Int { .. } => INT_NUMBER,
            ra_rustc_lexer::LiteralKind::Float { .. } => FLOAT_NUMBER,
            ra_rustc_lexer::LiteralKind::Char { .. } => CHAR,
            ra_rustc_lexer::LiteralKind::Byte { .. } => BYTE,
            ra_rustc_lexer::LiteralKind::Str { .. } => STRING,
            ra_rustc_lexer::LiteralKind::ByteStr { .. } => BYTE_STRING,
            ra_rustc_lexer::LiteralKind::RawStr { .. } => RAW_STRING,
            ra_rustc_lexer::LiteralKind::RawByteStr { .. } => RAW_BYTE_STRING,
        },
        _ => return None,
    };
    Some(Token { kind, len: TextUnit::from_usize(t.len) })
}
<commit_msg>Reduce code duplication in lexer by introducing small macro<commit_after>use crate::{
    SyntaxKind::{self, *},
    TextUnit,
};

/// A token of Rust source.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Token {
    /// The kind of token.
    pub kind: SyntaxKind,
    /// The length of the token.
    pub len: TextUnit,
}

macro_rules! match_literal_kind {
    ($kind:expr) => {
        match $kind {
                ra_rustc_lexer::LiteralKind::Int { .. } => INT_NUMBER,
                ra_rustc_lexer::LiteralKind::Float { .. } => FLOAT_NUMBER,
                ra_rustc_lexer::LiteralKind::Char { .. } => CHAR,
                ra_rustc_lexer::LiteralKind::Byte { .. } => BYTE,
                ra_rustc_lexer::LiteralKind::Str { .. } => STRING,
                ra_rustc_lexer::LiteralKind::ByteStr { .. } => BYTE_STRING,
                ra_rustc_lexer::LiteralKind::RawStr { .. } => RAW_STRING,
                ra_rustc_lexer::LiteralKind::RawByteStr { .. } => RAW_BYTE_STRING,
            }
    }
}

/// Break a string up into its component tokens
pub fn tokenize(text: &str) -> Vec<Token> {
    if text.is_empty() {
        return vec![];
    }
    let mut text = text;
    let mut acc = Vec::new();
    if let Some(len) = ra_rustc_lexer::strip_shebang(text) {
        acc.push(Token { kind: SHEBANG, len: TextUnit::from_usize(len) });
        text = &text[len..];
    }
    while !text.is_empty() {
        let rustc_token = ra_rustc_lexer::first_token(text);
        macro_rules! decompose {
            ($t1:expr, $t2:expr) => {{
                acc.push(Token { kind: $t1, len: 1.into() });
                acc.push(Token { kind: $t2, len: 1.into() });
                text = &text[2..];
                continue;
            }};
            ($t1:expr, $t2:expr, $t3:expr) => {{
                acc.push(Token { kind: $t1, len: 1.into() });
                acc.push(Token { kind: $t2, len: 1.into() });
                acc.push(Token { kind: $t3, len: 1.into() });
                text = &text[3..];
                continue;
            }};
        }
        let kind = match rustc_token.kind {
            ra_rustc_lexer::TokenKind::LineComment => COMMENT,
            ra_rustc_lexer::TokenKind::BlockComment { .. } => COMMENT,
            ra_rustc_lexer::TokenKind::Whitespace => WHITESPACE,
            ra_rustc_lexer::TokenKind::Ident => {
                let token_text = &text[..rustc_token.len];
                if token_text == "_" {
                    UNDERSCORE
                } else {
                    SyntaxKind::from_keyword(&text[..rustc_token.len]).unwrap_or(IDENT)
                }
            }
            ra_rustc_lexer::TokenKind::RawIdent => IDENT,
            ra_rustc_lexer::TokenKind::Literal { kind, .. } => match_literal_kind!(kind),
            ra_rustc_lexer::TokenKind::Lifetime { .. } => LIFETIME,
            ra_rustc_lexer::TokenKind::Semi => SEMI,
            ra_rustc_lexer::TokenKind::Comma => COMMA,
            ra_rustc_lexer::TokenKind::DotDotDot => decompose!(DOT, DOT, DOT),
            ra_rustc_lexer::TokenKind::DotDotEq => decompose!(DOT, DOT, EQ),
            ra_rustc_lexer::TokenKind::DotDot => decompose!(DOT, DOT),
            ra_rustc_lexer::TokenKind::Dot => DOT,
            ra_rustc_lexer::TokenKind::OpenParen => L_PAREN,
            ra_rustc_lexer::TokenKind::CloseParen => R_PAREN,
            ra_rustc_lexer::TokenKind::OpenBrace => L_CURLY,
            ra_rustc_lexer::TokenKind::CloseBrace => R_CURLY,
            ra_rustc_lexer::TokenKind::OpenBracket => L_BRACK,
            ra_rustc_lexer::TokenKind::CloseBracket => R_BRACK,
            ra_rustc_lexer::TokenKind::At => AT,
            ra_rustc_lexer::TokenKind::Pound => POUND,
            ra_rustc_lexer::TokenKind::Tilde => TILDE,
            ra_rustc_lexer::TokenKind::Question => QUESTION,
            ra_rustc_lexer::TokenKind::ColonColon => decompose!(COLON, COLON),
            ra_rustc_lexer::TokenKind::Colon => COLON,
            ra_rustc_lexer::TokenKind::Dollar => DOLLAR,
            ra_rustc_lexer::TokenKind::EqEq => decompose!(EQ, EQ),
            ra_rustc_lexer::TokenKind::Eq => EQ,
            ra_rustc_lexer::TokenKind::FatArrow => decompose!(EQ, R_ANGLE),
            ra_rustc_lexer::TokenKind::Ne => decompose!(EXCL, EQ),
            ra_rustc_lexer::TokenKind::Not => EXCL,
            ra_rustc_lexer::TokenKind::Le => decompose!(L_ANGLE, EQ),
            ra_rustc_lexer::TokenKind::LArrow => decompose!(COLON, MINUS),
            ra_rustc_lexer::TokenKind::Lt => L_ANGLE,
            ra_rustc_lexer::TokenKind::ShlEq => decompose!(L_ANGLE, L_ANGLE, EQ),
            ra_rustc_lexer::TokenKind::Shl => decompose!(L_ANGLE, L_ANGLE),
            ra_rustc_lexer::TokenKind::Ge => decompose!(R_ANGLE, EQ),
            ra_rustc_lexer::TokenKind::Gt => R_ANGLE,
            ra_rustc_lexer::TokenKind::ShrEq => decompose!(R_ANGLE, R_ANGLE, EQ),
            ra_rustc_lexer::TokenKind::Shr => decompose!(R_ANGLE, R_ANGLE),
            ra_rustc_lexer::TokenKind::RArrow => decompose!(MINUS, R_ANGLE),
            ra_rustc_lexer::TokenKind::Minus => MINUS,
            ra_rustc_lexer::TokenKind::MinusEq => decompose!(MINUS, EQ),
            ra_rustc_lexer::TokenKind::And => AMP,
            ra_rustc_lexer::TokenKind::AndAnd => decompose!(AMP, AMP),
            ra_rustc_lexer::TokenKind::AndEq => decompose!(AMP, EQ),
            ra_rustc_lexer::TokenKind::Or => PIPE,
            ra_rustc_lexer::TokenKind::OrOr => decompose!(PIPE, PIPE),
            ra_rustc_lexer::TokenKind::OrEq => decompose!(PIPE, EQ),
            ra_rustc_lexer::TokenKind::PlusEq => decompose!(PLUS, EQ),
            ra_rustc_lexer::TokenKind::Plus => PLUS,
            ra_rustc_lexer::TokenKind::StarEq => decompose!(STAR, EQ),
            ra_rustc_lexer::TokenKind::Star => STAR,
            ra_rustc_lexer::TokenKind::SlashEq => decompose!(SLASH, EQ),
            ra_rustc_lexer::TokenKind::Slash => SLASH,
            ra_rustc_lexer::TokenKind::CaretEq => decompose!(CARET, EQ),
            ra_rustc_lexer::TokenKind::Caret => CARET,
            ra_rustc_lexer::TokenKind::PercentEq => decompose!(PERCENT, EQ),
            ra_rustc_lexer::TokenKind::Percent => PERCENT,
            ra_rustc_lexer::TokenKind::Unknown => ERROR,
        };
        let token = Token { kind, len: TextUnit::from_usize(rustc_token.len) };
        acc.push(token);
        text = &text[rustc_token.len..];
    }
    acc
}

pub fn classify_literal(text: &str) -> Option<Token> {
    let t = ra_rustc_lexer::first_token(text);
    if t.len != text.len() {
        return None;
    }
    let kind = match t.kind {
        ra_rustc_lexer::TokenKind::Literal { kind, .. } => match_literal_kind!(kind),
        _ => return None,
    };
    Some(Token { kind, len: TextUnit::from_usize(t.len) })
}
<|endoftext|>


<commit_before>use core::panic;
//
use std::fs;
use std::sync::mpsc::channel;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::Sender;
use std::thread;

use parking_lot::RwLock;
use std::rc::Rc;
use std::sync::Arc;

#[macro_use]
pub(crate) mod macros;

pub mod codec;
pub mod codepointinfo;
pub mod config;
pub mod document;
pub mod editor;
pub mod event;
pub mod mapped_file;
pub mod modes;
pub mod screen;
pub mod view;

use crate::core::config::Config;
use crate::core::document::Document;
use crate::core::editor::Editor;
use crate::core::editor::EditorEnv;
use crate::core::event::Event;
use crate::core::event::EventMessage;
use crate::core::view::View;

pub const VERSION: &str = env!("CARGO_PKG_VERSION");

use std::sync::atomic::{AtomicUsize, Ordering};

//
pub static DBG_PRINTLN_FLAG: AtomicUsize = AtomicUsize::new(0);

pub fn enable_dbg_println() {
    DBG_PRINTLN_FLAG.store(1, Ordering::Relaxed);
}
pub fn disable_dbg_println() {
    DBG_PRINTLN_FLAG.store(0, Ordering::Relaxed);
}
pub fn toggle_dbg_println() {
    let v = DBG_PRINTLN_FLAG.load(Ordering::Relaxed);
    if v != 0 {
        disable_dbg_println();
    } else {
        enable_dbg_println();
    }
}

//
pub static USE_READ_CACHE: AtomicUsize = AtomicUsize::new(1);
pub fn disable_read_cache() {
    USE_READ_CACHE.store(0, Ordering::Relaxed);
}

pub fn use_read_cache() -> bool {
    USE_READ_CACHE.load(Ordering::Relaxed) != 0
}

//
pub static USE_BYTE_INDEX: AtomicUsize = AtomicUsize::new(1);
pub fn disable_byte_index() {
    USE_BYTE_INDEX.store(0, Ordering::Relaxed);
}

pub fn use_byte_index() -> bool {
    USE_BYTE_INDEX.load(Ordering::Relaxed) != 0
}

//
pub static BENCH_TO_EOF: AtomicUsize = AtomicUsize::new(0);
pub fn enable_bench_to_eof() {
    BENCH_TO_EOF.store(1, Ordering::Relaxed);
}
pub fn bench_to_eof() -> bool {
    BENCH_TO_EOF.load(Ordering::Relaxed) != 0
}

//
pub static RAW_FILTER_TO_SCREEN: AtomicUsize = AtomicUsize::new(0);
pub fn enable_raw_data_filter_to_screen() {
    RAW_FILTER_TO_SCREEN.store(1, Ordering::Relaxed);
}
pub fn raw_data_filter_to_screen() -> bool {
    RAW_FILTER_TO_SCREEN.load(Ordering::Relaxed) != 0
}

//
pub static NO_UI_RENDER: AtomicUsize = AtomicUsize::new(0);
pub fn set_no_ui_render(b: bool) {
    if b {
        NO_UI_RENDER.store(1, Ordering::Relaxed);
    } else {
        NO_UI_RENDER.store(0, Ordering::Relaxed);
    }
}
pub fn no_ui_render() -> bool {
    NO_UI_RENDER.load(Ordering::Relaxed) != 0
}

/*
 TODO(ceg):

    "core-mode" {
        scrollbar-mode
        v-split-mode
        h-split-mode
    }

text-mode =
    "raw-data"
    "utf8-codec"
    "mark-mode"
    "undo-mode"
    ----------
    "wrap-line"
    "wrap-word"

    --------------
    "replay-mode"

    "tab-mode"
    "selection/high-light"
    "fold-mode"
    "search-mode"
    "regex-mode"

    "exec-bin-mode"

    "dir-mode"

    "shell-mode"

    "ffi-mode"

    "hex-mode"

    "follow-mode"

    split layout.rs -> modes

  document.predefined_modes() Option<vec["internal:welcome-mode"]>
  document.predefined_modes() Option<vec["internal:debug-message"]>

  based on extension we will load predefine modes / keywords list etc ..

    replay-mode
      ctrl+x ctrl+x r :  {left,right} alt+{left,right}
*/

pub static WELCOME_MESSAGE: &str = r#"-*- Welcome to unlimitED! -*-

unlimitED! is an experimental text editor (running in the terminal).

SYNOPSIS
unlimited [options] [file ..]

It comes with:

  - basic UTF-8 support
  - very large file support
  - "infinite" undo/redo
  - multi-cursors
  - mouse selection (X11 terminal)

[Quit]
    Quit:           => ctrl+x ctrl+c

    Quit (no save)  => ctrl+x ctrl+q

    NB: quit will wait for large file(s) sync to storage.

[Moves]
    Left            =>
    Right           =>
    Up              =>
    Down            =>

[Edit]
    ctrl+o          => Open file

    ctrl+u          => Undo
    ctrl+r          => Redo

[Selection/Copy/Paste]
    with the keyboard:

    with the mouse (X11 terminal):

[Save]
    ctrl+x ctrl+s   => Save
                    synchronization of large file(s) does not block the ui.
                    the the is in "synchronization"

[Document Selection]

NB: unlimitED! comes with ABSOLUTELY NO WARRANTY
"#;

/// This function is the core of the editor.
/// It should be ran in an other thread than the main one (which is kept for ui)
pub fn run<'a>(
    config: Config,
    core_rx: &Receiver<EventMessage<'static>>,
    core_tx: &Sender<EventMessage<'static>>,
    ui_tx: &Sender<EventMessage<'static>>,
) {
    let (worker_tx, worker_rx) = channel();
    let (indexer_tx, indexer_rx) = channel();

    let mut editor = Editor::new(
        config,
        core_tx.clone(),
        ui_tx.clone(),
        worker_tx.clone(),
        indexer_tx.clone(),
    );
    let mut env = EditorEnv::new();

    // create worker thread
    let worker_th = {
        let core_tx = core_tx.clone();
        Some(thread::spawn(move || worker(&worker_rx, &core_tx)))
    };
    let indexer_th = {
        let core_tx = core_tx.clone();
        Some(thread::spawn(move || indexer(&indexer_rx, &core_tx)))
    };

    editor.worker_tx = worker_tx.clone();

    load_modes(&mut editor, &mut env);

    load_files(&mut editor, &mut env);

    create_views(&mut editor, &mut env);

    editor::main_loop(&mut editor, &mut env, &core_rx, &ui_tx);

    // wait for worker thread
    if let Some(worker_handle) = worker_th {
        worker_handle.join().unwrap()
    }
    if let Some(indexer_handle) = indexer_th {
        indexer_handle.join().unwrap()
    }
}

/////////////////////// worker.rs

pub fn worker(
    worker_rx: &Receiver<EventMessage<'static>>,
    _core_tx: &Sender<EventMessage<'static>>,
) {
    dbg_println!("[starting worker thread]");
    loop {
        if let Ok(evt) = worker_rx.recv() {
            match evt.event {
                Event::ApplicationQuitEvent => {
                    dbg_println!("[stopping worker thread]");
                    break;
                }

                Event::SyncTask { doc } => {
                    document::sync_to_storage(&doc);
                }

                //                Event::OutsourcedTask { task_uid,  editor, editor_env, doc_id, vid, action, params ? } => {
                //                    action();
                //                }
                _ => {
                    panic!("worker thread received an unexpected message");
                }
            }
        }
    }
}

pub fn indexer(
    worker_rx: &Receiver<EventMessage<'static>>,
    core_tx: &Sender<EventMessage<'static>>,
) {
    if !use_byte_index() {
        return;
    }

    dbg_println!("[starting worker thread (indexer)]");
    loop {
        if let Ok(evt) = worker_rx.recv() {
            match evt.event {
                Event::ApplicationQuitEvent => {
                    dbg_println!("[stopping worker thread]");
                    break;
                }

                // TODO(ceg): split in sub-threads/async task
                Event::IndexTask { document_map } => {
                    dbg_println!("[receive index task ]");

                    let mut refresh_ui = false;
                    let map = document_map.read();
                    let mut t0 = std::time::Instant::now();
                    for (_id, doc) in map.iter() {
                        document::build_index(doc);
                        refresh_ui = true;
                        let t1 = std::time::Instant::now();
                        if (t1 - t0).as_millis() > 1000 {
                            // send ui refresh event
                            let msg = EventMessage::new(0, Event::RefreshViewEvent);
                            core_tx.send(msg).unwrap_or(());

                            refresh_ui = false;
                            t0 = t1;
                        }
                    }

                    // last ui refresh
                    if refresh_ui {
                        let msg = EventMessage::new(0, Event::RefreshViewEvent);
                        core_tx.send(msg).unwrap_or(());
                    }
                }

                _ => {
                    panic!("worker thread received an unexpected message");
                }
            }
        }
    }
}

use crate::core::document::DocumentBuilder;

/// TODO(ceg): replace this by load/unload doc functions
/// the ui will open the documents on demand
pub fn load_files(editor: &mut Editor<'static>, env: &mut EditorEnv<'static>) {
    let mut id = editor.document_map.read().len();

    for f in &editor.config.files_list {
        // check file type
        if let Ok(metadata) = fs::metadata(f) {
            let file_type = metadata.file_type();

            // ignore directories for now
            if file_type.is_dir() {
                continue;
            }
        } else {
            // log error
            eprintln!("cannot check {} file type", f);
            continue;
        }

        let b = DocumentBuilder::new()
            .document_name(f)
            .file_name(f)
            .internal(false)
            .finalize();

        if let Some(b) = b {
            let doc_id = document::Id(id);
            b.as_ref().write().id = doc_id; // TODO(ceg): improve doc id generation
            editor.document_map.write().insert(doc_id, b);
            id += 1;
        }
    }

    // default buffer ?
    let map_is_empty = editor.document_map.read().is_empty();
    if map_is_empty {
        // edit.get_untitled_count() -> 1

        let b = DocumentBuilder::new()
            .document_name("untitled-1")
            .internal(false)
            .finalize();
        if let Some(b) = b {
            {
                let mut d = b.write();
                let s = WELCOME_MESSAGE.as_bytes();

                // move 1st tag to ctor/doc::new() ?
                d.tag(env.current_time, 0, vec![0]); // TODO(ceg): rm this only if the buffer log is cleared
                                                     //    create_views(&mut editor, &mut env);

                d.insert(0, s.len(), s);

                // do not allow to go back to empty buffer
                d.buffer_log_reset();
                d.changed = false;
            }
            let doc_id = document::Id(id);
            editor.document_map.write().insert(doc_id, b);
            id += 1;
        }
    }

    // configure document
    let modes = editor.modes.clone();
    for (mode_name, mode) in modes.borrow().iter() {
        // per mode document metadata
        dbg_println!("setup mode[{}] document metadata", mode_name);
        let mut mode = mode.borrow_mut();
        let map = editor.document_map.clone();
        let mut map = map.as_ref().write();
        for (_, doc) in map.iter_mut() {
            let mut doc = doc.write();
            mode.configure_document(editor, env, &mut doc);
        }
    }

    dbg_println!("id {}", id);
}

pub fn create_views(mut editor: &mut Editor<'static>, mut env: &mut EditorEnv<'static>) {
    let document_map = editor.document_map.clone();
    let document_map = document_map.read();

    // create default views
    // sort by arg pos first
    let mut docs_id: Vec<document::Id> = document_map.iter().map(|(k, _v)| *k).collect();
    docs_id.sort();
    let mut docs: Vec<Arc<RwLock<Document>>> = vec![];
    for id in docs_id.iter() {
        if let Some(doc) = document_map.get(id) {
            docs.push(Arc::clone(doc));
        }
    }

    let modes = match std::env::var("SINGLE_VIEW") {
        Ok(_) => vec!["simple-view".to_owned()],
        _ => vec!["basic-editor".to_owned()],
    };

    // create views
    for doc in docs {
        let view = View::new(
            &mut editor,
            &mut env,
            None,
            0,
            0,
            1,
            1,
            Some(doc),
            &modes,
            0,
        );
        dbg_println!("create {:?}", view.id);

        // top level views
        editor.root_views.push(view.id);
        editor.view_map.insert(view.id, Rc::new(RwLock::new(view)));
    }

    // index documents
    // TODO(ceg): send one event per doc
    if true {
        let msg = EventMessage {
            seq: 0,
            event: Event::IndexTask {
                document_map: Arc::clone(&editor.document_map),
            },
        };
        editor.indexer_tx.send(msg).unwrap_or(());
    }
}

use crate::core::modes::BasicEditorMode;
use crate::core::modes::SimpleViewMode;

use crate::core::modes::CoreMode;
use crate::core::modes::FindMode;
use crate::core::modes::TextMode;

use crate::core::modes::StatusMode;

use crate::core::modes::HsplitMode;
use crate::core::modes::VsplitMode;

use crate::core::modes::VscrollbarMode;

use crate::core::modes::LineNumberMode;

pub fn load_modes(editor: &mut Editor, _env: &mut EditorEnv) {
    // set default mode(s)
    editor.register_mode(Box::new(CoreMode::new()));
    editor.register_mode(Box::new(BasicEditorMode::new()));
    editor.register_mode(Box::new(SimpleViewMode::new()));

    editor.register_mode(Box::new(VsplitMode::new()));
    editor.register_mode(Box::new(HsplitMode::new()));

    editor.register_mode(Box::new(VscrollbarMode::new()));

    editor.register_mode(Box::new(TextMode::new()));
    editor.register_mode(Box::new(StatusMode::new()));

    editor.register_mode(Box::new(FindMode::new()));

    editor.register_mode(Box::new(LineNumberMode::new()));
}
<commit_msg>update help<commit_after>use core::panic;
//
use std::fs;
use std::sync::mpsc::channel;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::Sender;
use std::thread;

use parking_lot::RwLock;
use std::rc::Rc;
use std::sync::Arc;

#[macro_use]
pub(crate) mod macros;

pub mod codec;
pub mod codepointinfo;
pub mod config;
pub mod document;
pub mod editor;
pub mod event;
pub mod mapped_file;
pub mod modes;
pub mod screen;
pub mod view;

use crate::core::config::Config;
use crate::core::document::Document;
use crate::core::editor::Editor;
use crate::core::editor::EditorEnv;
use crate::core::event::Event;
use crate::core::event::EventMessage;
use crate::core::view::View;

pub const VERSION: &str = env!("CARGO_PKG_VERSION");

use std::sync::atomic::{AtomicUsize, Ordering};

//
pub static DBG_PRINTLN_FLAG: AtomicUsize = AtomicUsize::new(0);

pub fn enable_dbg_println() {
    DBG_PRINTLN_FLAG.store(1, Ordering::Relaxed);
}
pub fn disable_dbg_println() {
    DBG_PRINTLN_FLAG.store(0, Ordering::Relaxed);
}
pub fn toggle_dbg_println() {
    let v = DBG_PRINTLN_FLAG.load(Ordering::Relaxed);
    if v != 0 {
        disable_dbg_println();
    } else {
        enable_dbg_println();
    }
}

//
pub static USE_READ_CACHE: AtomicUsize = AtomicUsize::new(1);
pub fn disable_read_cache() {
    USE_READ_CACHE.store(0, Ordering::Relaxed);
}

pub fn use_read_cache() -> bool {
    USE_READ_CACHE.load(Ordering::Relaxed) != 0
}

//
pub static USE_BYTE_INDEX: AtomicUsize = AtomicUsize::new(1);
pub fn disable_byte_index() {
    USE_BYTE_INDEX.store(0, Ordering::Relaxed);
}

pub fn use_byte_index() -> bool {
    USE_BYTE_INDEX.load(Ordering::Relaxed) != 0
}

//
pub static BENCH_TO_EOF: AtomicUsize = AtomicUsize::new(0);
pub fn enable_bench_to_eof() {
    BENCH_TO_EOF.store(1, Ordering::Relaxed);
}
pub fn bench_to_eof() -> bool {
    BENCH_TO_EOF.load(Ordering::Relaxed) != 0
}

//
pub static RAW_FILTER_TO_SCREEN: AtomicUsize = AtomicUsize::new(0);
pub fn enable_raw_data_filter_to_screen() {
    RAW_FILTER_TO_SCREEN.store(1, Ordering::Relaxed);
}
pub fn raw_data_filter_to_screen() -> bool {
    RAW_FILTER_TO_SCREEN.load(Ordering::Relaxed) != 0
}

//
pub static NO_UI_RENDER: AtomicUsize = AtomicUsize::new(0);
pub fn set_no_ui_render(b: bool) {
    if b {
        NO_UI_RENDER.store(1, Ordering::Relaxed);
    } else {
        NO_UI_RENDER.store(0, Ordering::Relaxed);
    }
}
pub fn no_ui_render() -> bool {
    NO_UI_RENDER.load(Ordering::Relaxed) != 0
}

/*
 TODO(ceg):

    "core-mode" {
        scrollbar-mode
        v-split-mode
        h-split-mode
    }

text-mode =
    "raw-data"
    "utf8-codec"
    "mark-mode"
    "undo-mode"
    ----------
    "wrap-line"
    "wrap-word"

    --------------
    "replay-mode"

    "tab-mode"
    "selection/high-light"
    "fold-mode"
    "search-mode"
    "regex-mode"

    "exec-bin-mode"

    "dir-mode"

    "shell-mode"

    "ffi-mode"

    "hex-mode"

    "follow-mode"

    split layout.rs -> modes

  document.predefined_modes() Option<vec["internal:welcome-mode"]>
  document.predefined_modes() Option<vec["internal:debug-message"]>

  based on extension we will load predefine modes / keywords list etc ..

    replay-mode
      ctrl+x ctrl+x r :  {left,right} alt+{left,right}
*/

pub static WELCOME_MESSAGE: &str = r#"-*- Welcome to unlimitED! -*-

unlimitED! is an experimental text editor (running in the terminal).

SYNOPSIS
unlimited [options] [file ..]

It comes with:

  - basic UTF-8 support
  - very large file support
  - "infinite" undo/redo
  - multi-cursors
  - mouse selection (X11 terminal)

[Quit]
    Quit:           => ctrl+x ctrl+c

    Quit (no save)  => ctrl+x ctrl+q

    NB: quit will wait for large file(s) sync to storage.

[Moves]
    Left            =>
    Right           =>
    Up              =>
    Down            =>

[Edit]
    ctrl+o          => Open file (TODO)

    ctrl+u          => Undo
    ctrl+r          => Redo

[Selection/Copy/Paste]
    with the keyboard:

    with the mouse (X11 terminal):

[Save]
    ctrl+x ctrl+s   => Save
                    synchronization of large file(s) is done in the background and does not block the ui.

[Document Selection]

NB: unlimitED! comes with ABSOLUTELY NO WARRANTY
"#;

/// This function is the core of the editor.
/// It should be ran in an other thread than the main one (which is kept for ui)
pub fn run<'a>(
    config: Config,
    core_rx: &Receiver<EventMessage<'static>>,
    core_tx: &Sender<EventMessage<'static>>,
    ui_tx: &Sender<EventMessage<'static>>,
) {
    let (worker_tx, worker_rx) = channel();
    let (indexer_tx, indexer_rx) = channel();

    let mut editor = Editor::new(
        config,
        core_tx.clone(),
        ui_tx.clone(),
        worker_tx.clone(),
        indexer_tx.clone(),
    );
    let mut env = EditorEnv::new();

    // create worker thread
    let worker_th = {
        let core_tx = core_tx.clone();
        Some(thread::spawn(move || worker(&worker_rx, &core_tx)))
    };
    let indexer_th = {
        let core_tx = core_tx.clone();
        Some(thread::spawn(move || indexer(&indexer_rx, &core_tx)))
    };

    editor.worker_tx = worker_tx.clone();

    load_modes(&mut editor, &mut env);

    load_files(&mut editor, &mut env);

    create_views(&mut editor, &mut env);

    editor::main_loop(&mut editor, &mut env, &core_rx, &ui_tx);

    // wait for worker thread
    if let Some(worker_handle) = worker_th {
        worker_handle.join().unwrap()
    }
    if let Some(indexer_handle) = indexer_th {
        indexer_handle.join().unwrap()
    }
}

/////////////////////// worker.rs

pub fn worker(
    worker_rx: &Receiver<EventMessage<'static>>,
    _core_tx: &Sender<EventMessage<'static>>,
) {
    dbg_println!("[starting worker thread]");
    loop {
        if let Ok(evt) = worker_rx.recv() {
            match evt.event {
                Event::ApplicationQuitEvent => {
                    dbg_println!("[stopping worker thread]");
                    break;
                }

                Event::SyncTask { doc } => {
                    document::sync_to_storage(&doc);
                }

                //                Event::OutsourcedTask { task_uid,  editor, editor_env, doc_id, vid, action, params ? } => {
                //                    action();
                //                }
                _ => {
                    panic!("worker thread received an unexpected message");
                }
            }
        }
    }
}

pub fn indexer(
    worker_rx: &Receiver<EventMessage<'static>>,
    core_tx: &Sender<EventMessage<'static>>,
) {
    if !use_byte_index() {
        return;
    }

    dbg_println!("[starting worker thread (indexer)]");
    loop {
        if let Ok(evt) = worker_rx.recv() {
            match evt.event {
                Event::ApplicationQuitEvent => {
                    dbg_println!("[stopping worker thread]");
                    break;
                }

                // TODO(ceg): split in sub-threads/async task
                Event::IndexTask { document_map } => {
                    dbg_println!("[receive index task ]");

                    let mut refresh_ui = false;
                    let map = document_map.read();
                    let mut t0 = std::time::Instant::now();
                    for (_id, doc) in map.iter() {
                        document::build_index(doc);
                        refresh_ui = true;
                        let t1 = std::time::Instant::now();
                        if (t1 - t0).as_millis() > 1000 {
                            // send ui refresh event
                            let msg = EventMessage::new(0, Event::RefreshViewEvent);
                            core_tx.send(msg).unwrap_or(());

                            refresh_ui = false;
                            t0 = t1;
                        }
                    }

                    // last ui refresh
                    if refresh_ui {
                        let msg = EventMessage::new(0, Event::RefreshViewEvent);
                        core_tx.send(msg).unwrap_or(());
                    }
                }

                _ => {
                    panic!("worker thread received an unexpected message");
                }
            }
        }
    }
}

use crate::core::document::DocumentBuilder;

/// TODO(ceg): replace this by load/unload doc functions
/// the ui will open the documents on demand
pub fn load_files(editor: &mut Editor<'static>, env: &mut EditorEnv<'static>) {
    let mut id = editor.document_map.read().len();

    for f in &editor.config.files_list {
        // check file type
        if let Ok(metadata) = fs::metadata(f) {
            let file_type = metadata.file_type();

            // ignore directories for now
            if file_type.is_dir() {
                continue;
            }
        } else {
            // log error
            eprintln!("cannot check {} file type", f);
            continue;
        }

        let b = DocumentBuilder::new()
            .document_name(f)
            .file_name(f)
            .internal(false)
            .finalize();

        if let Some(b) = b {
            let doc_id = document::Id(id);
            b.as_ref().write().id = doc_id; // TODO(ceg): improve doc id generation
            editor.document_map.write().insert(doc_id, b);
            id += 1;
        }
    }

    // default buffer ?
    let map_is_empty = editor.document_map.read().is_empty();
    if map_is_empty {
        // edit.get_untitled_count() -> 1

        let b = DocumentBuilder::new()
            .document_name("untitled-1")
            .internal(false)
            .finalize();
        if let Some(b) = b {
            {
                let mut d = b.write();
                let s = WELCOME_MESSAGE.as_bytes();

                // move 1st tag to ctor/doc::new() ?
                d.tag(env.current_time, 0, vec![0]); // TODO(ceg): rm this only if the buffer log is cleared
                                                     //    create_views(&mut editor, &mut env);

                d.insert(0, s.len(), s);

                // do not allow to go back to empty buffer
                d.buffer_log_reset();
                d.changed = false;
            }
            let doc_id = document::Id(id);
            editor.document_map.write().insert(doc_id, b);
            id += 1;
        }
    }

    // configure document
    let modes = editor.modes.clone();
    for (mode_name, mode) in modes.borrow().iter() {
        // per mode document metadata
        dbg_println!("setup mode[{}] document metadata", mode_name);
        let mut mode = mode.borrow_mut();
        let map = editor.document_map.clone();
        let mut map = map.as_ref().write();
        for (_, doc) in map.iter_mut() {
            let mut doc = doc.write();
            mode.configure_document(editor, env, &mut doc);
        }
    }

    dbg_println!("id {}", id);
}

pub fn create_views(mut editor: &mut Editor<'static>, mut env: &mut EditorEnv<'static>) {
    let document_map = editor.document_map.clone();
    let document_map = document_map.read();

    // create default views
    // sort by arg pos first
    let mut docs_id: Vec<document::Id> = document_map.iter().map(|(k, _v)| *k).collect();
    docs_id.sort();
    let mut docs: Vec<Arc<RwLock<Document>>> = vec![];
    for id in docs_id.iter() {
        if let Some(doc) = document_map.get(id) {
            docs.push(Arc::clone(doc));
        }
    }

    let modes = match std::env::var("SINGLE_VIEW") {
        Ok(_) => vec!["simple-view".to_owned()],
        _ => vec!["basic-editor".to_owned()],
    };

    // create views
    for doc in docs {
        let view = View::new(
            &mut editor,
            &mut env,
            None,
            0,
            0,
            1,
            1,
            Some(doc),
            &modes,
            0,
        );
        dbg_println!("create {:?}", view.id);

        // top level views
        editor.root_views.push(view.id);
        editor.view_map.insert(view.id, Rc::new(RwLock::new(view)));
    }

    // index documents
    // TODO(ceg): send one event per doc
    if true {
        let msg = EventMessage {
            seq: 0,
            event: Event::IndexTask {
                document_map: Arc::clone(&editor.document_map),
            },
        };
        editor.indexer_tx.send(msg).unwrap_or(());
    }
}

use crate::core::modes::BasicEditorMode;
use crate::core::modes::SimpleViewMode;

use crate::core::modes::CoreMode;
use crate::core::modes::FindMode;
use crate::core::modes::TextMode;

use crate::core::modes::StatusMode;

use crate::core::modes::HsplitMode;
use crate::core::modes::VsplitMode;

use crate::core::modes::VscrollbarMode;

use crate::core::modes::LineNumberMode;

pub fn load_modes(editor: &mut Editor, _env: &mut EditorEnv) {
    // set default mode(s)
    editor.register_mode(Box::new(CoreMode::new()));
    editor.register_mode(Box::new(BasicEditorMode::new()));
    editor.register_mode(Box::new(SimpleViewMode::new()));

    editor.register_mode(Box::new(VsplitMode::new()));
    editor.register_mode(Box::new(HsplitMode::new()));

    editor.register_mode(Box::new(VscrollbarMode::new()));

    editor.register_mode(Box::new(TextMode::new()));
    editor.register_mode(Box::new(StatusMode::new()));

    editor.register_mode(Box::new(FindMode::new()));

    editor.register_mode(Box::new(LineNumberMode::new()));
}
<|endoftext|>


<commit_before>//!
//! A game engine based around a state machine
//!

// tcod
//
// This library helps us create a window to render to, gives us functions for pathing, FOV, and
// generating dungeons (though we don't use their dungeon generators because I am much more interested in building my own.
// See the `Dungeon` module.)
extern crate tcod;
// We use tcod here to create the root console (to give it to the renderer) and to grab inputs from the player
use self::tcod::{console, Console};
use self::tcod::input;

// The game log
//
// Every roguelike needs a way of delivering messages to the player because - spoiler - ASCII isn't exactly expressive enough
// to alert the player as to their surroundings or the goings on of the game world they inhabit. The log system primarily is held
// together by equal parts eldritch rust knowledge and voodoo magic and each time I use it I am both simultaneously shocked at
// how hacky it is and how good rust is at handling it.
//
// Log is imported first so all other modules can get the macro
#[macro_use]
pub mod log;
use self::log::GlobalLog;

// The world handler
//
// The world is in charge of creating dungeons, populating them with creatures and items, and keeping track of the player.
// It basically ties together all game subsystems into a concrete package, hence, world.
//
// World is public so that docs are generated for it
pub mod world;
// Import world directly so we can make instances of it
use self::world::World;
use self::world::dungeon::map::Pos;

// Creatures
//
// Creatures are things like monsters
//
pub mod creature;
// While normally this module most likely should not have access to objects, we need to see `Action`s as the player's
// choices changes the state of the game
use self::creature::actions::Actions;

// Items
pub mod item;
use self::item::{ItemProperty, money_value};

// Renderer
//
// The renderer is the interface by which game objects and constructs are made real through tcod interaction. The central
// idea is that if tcod is ever abandoned, minimal ammounts of code outside of the renderer should have to be changed.
// This hasn't *really* been the case as `World` depends on tcod lighting maps, the `Engine` holds a tcod root, and init creates
// said root.
//
// Renderer is public so that docs are generated for it
pub mod renderer;
// We import the renderer to create instances of it and RGB so we can color some log outputs
use self::renderer::Renderer;
use self::renderer::RGB;

// Initializer
//
// All things must be created, and all configurations must be loaded at some point. The initializer module prepares tcod consoles,
// and loads the configs with serde
//
// Pretty sure you understand why this is public by now
pub mod init;

///
/// Enum representing the state of the game
///
/// "All programs are just one big finite automata" - Nowak, probably
///
/// All games essentially boil down to a number of finite states and the transitions between them. To help decide what state
/// the game transitions to, or to make other choices regarding the game, some notion of a state has to be preserved to base
/// such a judgement off of. Most of the time however, the player is the main driving factor of the state changes.
///
pub enum State {
  // Game just created
  New,
  // All possible player actions are also valid states for the game to be in
  Act(Actions),
  // A key was pressed
  Keypress,
  // Debug command was triggered
  Debug
}

///
/// Engine struct to package the world with the renderer as well as any debug flags we may want to add
///
/// This engine isn't really a typical "game engine" in the way unreal or unity are but more or less what actually causes the game
/// to even be playable in the first place, as world elements are wrapped together with a renderer and input. It essentially bridges the gap
/// between physical user and the game world via the renderer, and in that sense it's more of a steering wheel than an engine, but
/// `SteeringWheel` makes pretty much no sense whatsoever and "engine" is already within the common vernacular of game developers
///
pub struct Engine {
  world: World,
  state: State,
  ren: Renderer,
  root: console::Root,

  // Debug options the engine tracks
  noclip: bool

}

impl Engine {

  ///
  /// Capture keyboard input from tcod and update player state
  ///
  fn process_keypress(&mut self, keypress: input::Key) {

    match keypress.code {

      // If the keycode isn't escape we continue checking for important keys
      input::KeyCode::Escape => panic!("Bye"),

      // This part of the code is for capturing the keypress not as an object, but as a character for easier parsing
      _ => {

        // We only care if the key is printable, aka, has some symbol attached to it
        if keypress.printable != ' ' {

          // First, make an assumption that the player is affecting their movement as 90% of the game
          // is walking around. We *could* add it to every single one of the vim keypresses to save a trivial ammount of
          // time assigning this variable, but I dislike that.
          let oldpos = self.world.player.actor.pos.clone();

          // In addition, update the game state
          // Important so that if the game state becomes debug we can leave that state instantly
          self.state = State::Keypress;

          // Begin to pattern match the char corresponding to the key pressed
          match keypress.printable {

            // Movement keys are bound to vim-like controls (hjklyubn)
            'h' => {
              self.world.player.actor.move_cart(-1, 0);
              self.world.player.state = Actions::Move;
            },
            'j' => {
              self.world.player.actor.move_cart(0, 1);
              self.world.player.state = Actions::Move;
            },
            'k' => {
              self.world.player.actor.move_cart(0, -1);
              self.world.player.state = Actions::Move;
            },
            'l' => {
              self.world.player.actor.move_cart(1, 0);
              self.world.player.state = Actions::Move;
            },
            'y' => {
              self.world.player.actor.move_cart(-1, -1);
              self.world.player.state = Actions::Move;
            },
            'u' => {
              self.world.player.actor.move_cart(1, -1);
              self.world.player.state = Actions::Move;
            },
            'b' => {
              self.world.player.actor.move_cart(-1, 1);
              self.world.player.state = Actions::Move;
            },
            'n' => {
              self.world.player.actor.move_cart(1, 1);
              self.world.player.state = Actions::Move;
            },

            // Pick up item
            'g' => {
              // Player pos
              let player_pos = self.world.player.actor.pos.clone();
              // Get items at players feet
              // let items_at_feet = self.world.floor.items.iter().filter(|item| item.pos == self.world.player.actor.pos.clone());
              for item in &self.world.floor.items {
                if item.pos == player_pos {
                  match item.property {
                    ItemProperty::Money(ref tender) => self.world.player.wallet += money_value(&tender)
                  }
                }
              }
              // Prune picked up items
              self.world.floor.items.retain( |item| item.pos != player_pos );
            }

            // Force reload word
            'w' => {
              self.world = World::new(Pos::from_tup(init::map_dimensions()));
              self.state = State::Act(Actions::Unknown);
            },
            // Create an empty level for testing
            'q' => {
              self.world.test_empty();
              self.state = State::Act(Actions::Unknown);
            },
            // Wait
            '.' => {
              self.world.player.state = Actions::Wait;
            },

            // Go downstars (if possible)
            '>' => { self.world.player.state = Actions::DownStair },
            // Go upstairs (if possible)
            '<' => { self.world.player.state = Actions::UpStair },

            // Debug keypresses

            // Toggle scent
            'r' => {
              self.ren.show_scent = !self.ren.show_scent;
              self.ren.draw_all(&mut self.root, &mut self.world);
              self.state = State::Debug;
            },

            // Toggle sound
            't' => {
              self.ren.show_sound = !self.ren.show_sound;
              self.ren.draw_all(&mut self.root, &mut self.world);
              self.state = State::Debug;
            },

            // Toggle FoV
            'f' => {
              self.ren.fov = !self.ren.fov;
              self.ren.draw_all(&mut self.root, &mut self.world);
              self.state = State::Debug;
            },

            // Toggle noclip
            'z' => {
              self.noclip = !self.noclip;
              self.state = State::Debug;
            },

            // Tcod test
            'c' => {
              self.ren.tcod_test(&mut self.root);
              self.state = State::Debug;
            },

            // Unbound key, so we just say we don't know what the player did
            _ => { self.world.player.state = Actions::Unknown }

          }

          // Now the game state needs to be properly re-oriented based on the (potential) player action.
          // In addition, we should also process the action of the player while we're here
          match self.state {

            // If state is Debug, don't override
            State::Debug => (),

            _ => {

              // Set game state to player state
              self.state = State::Act(self.world.player.state.clone());

              // Now let's process the player's action
              match self.world.player.state {

                Actions::Move => {

                  // Make sure player doesn't do anything dumb
                  if !self.world.is_valid_pos(self.world.player.actor.pos.x, self.world.player.actor.pos.y) && !self.noclip {
                    self.world.player.actor.pos = oldpos;
                    self.world.player.state = Actions::Unknown;
                  // Otherwise if the position is valid...
                  } else {
                    // Empty for right now
                  }

                }

                _ => ()

              }

            }
          }

        }

        // Prints keycode to console in case if you're trying to find a key that isn't intutive, or you're debugging
        // else {
        //   println!("{:?}", keypress.code);
        // }

      }

    }

  }

  ///
  /// Return a new `Engine`
  ///
  pub fn new() -> Self {

    // Get map height
    let map_dim = Pos::from_tup(init::map_dimensions());

    // Get root console
    let root = init::root();

    Engine {

      world: World::new(map_dim),

      state: State::New,

      ren: Renderer::new(
        map_dim,
        Pos::new(root.width() as isize, root.height() as isize),
        init::console_height(),
        init::panel_width()
      ),

      root: root,

      // Debug
      noclip: false

    }

  }

  ///
  /// Update the game state, then update the world depending on the new state
  ///
  fn update(&mut self) {

    match self.state {

      // Player moving or waiting prompts a world update
      State::Act(Actions::Move) | State::Act(Actions::Wait) => self.world.update(),

      // Trying to go up and downstairs prompts the respective response from world
      State::Act(Actions::DownStair) => {
        // No clip through floors
        if self.noclip {
          self.world.go_down();
        } else {
          self.world.player_go_down();
        }
      },

      State::Act(Actions::UpStair) => {
        // No clip through floors
        if self.noclip {
          self.world.go_up();
        } else {
          self.world.player_go_up();
        }
      }

      // All other variants are dropped
      _ => {}

    }

  }

  ///
  /// Title screen test
  ///
  fn title_screen(&mut self) {

    // First part of this pretty much just fills the screen with black

    let w = self.root.width().clone();
    let h = self.root.height().clone();

    for x in 0..w {
      for y in 0..h {
        self.root.put_char_ex(
          x as i32,
          y as i32,
          ' ',
          RGB(0, 0, 0).to_tcod(),
          RGB(0, 0, 0).to_tcod()
        );
      }
    }

    // Second part displays stuff

    let title = "Edgequest";
    let subtitle = "Press any key to start.";

    self.root.set_default_foreground(RGB(255, 255, 255).to_tcod());
    // i32 conversion is a pain since I'd rather store stuff as isize and the tcod lib wants i32 since it's
    // pretty much just a C++ interface which is annoying
    self.root.print((w / 2 - (title.len() / 2) as i32) as i32, (h / 2 - 1) as i32, title);
    self.root.print((w / 2 - (subtitle.len() / 2) as i32) as i32, (h / 2 + 1) as i32, subtitle);

    self.root.flush();

    // Wait for keypress
    let keypress = self.root.wait_for_keypress(true);

    // Escape on title should quit the game
    match keypress.code {

      // If the keycode isn't escape we continue checking for important keys
      input::KeyCode::Escape => panic!("Bye"),
      _ => {}

    }

  }

  ///
  /// Play the game.
  ///
  pub fn play(&mut self) {

    // Create the title screen
    self.title_screen();

    // Some starting messages, will be removed in later versions (hopefully)
    log!(("Welcome to Edgequest",                 RGB(255,   0, 255)));
    log!(("Move with vim keys",                   RGB(255, 255, 255)));
    log!(("esc to quit, w to regenerate the map", RGB(255, 255, 255)));
    log!(("r to toggle scent, t to toggle sound", RGB(255, 255, 255)));
    log!(("f to toggle FoV, z to toggle noclip",  RGB(255, 255, 255)));

    // Initial update
    self.update();

    // Draw all and capture keypresses
    while !self.root.window_closed() {

      // Draw what the camera sees
      self.ren.draw_all(&mut self.root, &mut self.world);

      // Capture game keys (Keys that change the state of the player)
      // This is what gives it the turn based nature, i.e. waits for player input before
      // doing anything. Process keypress also updates the engine state.
      //
      // We save input to keypress because directly sending the root back into the engine
      // causes it to be mutably borrowed more than once
      let keypress = self.root.wait_for_keypress(true);
      self.process_keypress(keypress);

      // Update engine based on state
      self.update();

    }

  }

}<commit_msg>Add wizard mode, fix money pickup bug<commit_after>//!
//! A game engine based around a state machine
//!

// tcod
//
// This library helps us create a window to render to, gives us functions for pathing, FOV, and
// generating dungeons (though we don't use their dungeon generators because I am much more interested in building my own.
// See the `Dungeon` module.)
extern crate tcod;
// We use tcod here to create the root console (to give it to the renderer) and to grab inputs from the player
use self::tcod::{console, Console};
use self::tcod::input;

// The game log
//
// Every roguelike needs a way of delivering messages to the player because - spoiler - ASCII isn't exactly expressive enough
// to alert the player as to their surroundings or the goings on of the game world they inhabit. The log system primarily is held
// together by equal parts eldritch rust knowledge and voodoo magic and each time I use it I am both simultaneously shocked at
// how hacky it is and how good rust is at handling it.
//
// Log is imported first so all other modules can get the macro
#[macro_use]
pub mod log;
use self::log::GlobalLog;

// The world handler
//
// The world is in charge of creating dungeons, populating them with creatures and items, and keeping track of the player.
// It basically ties together all game subsystems into a concrete package, hence, world.
//
// World is public so that docs are generated for it
pub mod world;
// Import world directly so we can make instances of it
use self::world::World;
use self::world::dungeon::map::Pos;

// Creatures
//
// Creatures are things like monsters
//
pub mod creature;
// While normally this module most likely should not have access to objects, we need to see `Action`s as the player's
// choices changes the state of the game
use self::creature::actions::Actions;

// Items
pub mod item;
use self::item::{ItemProperty, money_value};

// Renderer
//
// The renderer is the interface by which game objects and constructs are made real through tcod interaction. The central
// idea is that if tcod is ever abandoned, minimal ammounts of code outside of the renderer should have to be changed.
// This hasn't *really* been the case as `World` depends on tcod lighting maps, the `Engine` holds a tcod root, and init creates
// said root.
//
// Renderer is public so that docs are generated for it
pub mod renderer;
// We import the renderer to create instances of it and RGB so we can color some log outputs
use self::renderer::{Renderer, Renderable, RGB};

// Initializer
//
// All things must be created, and all configurations must be loaded at some point. The initializer module prepares tcod consoles,
// and loads the configs with serde
//
// Pretty sure you understand why this is public by now
pub mod init;

///
/// Enum representing the state of the game
///
/// "All programs are just one big finite automata" - Nowak, probably
///
/// All games essentially boil down to a number of finite states and the transitions between them. To help decide what state
/// the game transitions to, or to make other choices regarding the game, some notion of a state has to be preserved to base
/// such a judgement off of. Most of the time however, the player is the main driving factor of the state changes.
///
pub enum State {
  // Game just created
  New,
  // All possible player actions are also valid states for the game to be in
  Act(Actions),
  // A key was pressed
  Keypress,
  // Debug command was triggered
  Debug
}

///
/// Engine struct to package the world with the renderer as well as any debug flags we may want to add
///
/// This engine isn't really a typical "game engine" in the way unreal or unity are but more or less what actually causes the game
/// to even be playable in the first place, as world elements are wrapped together with a renderer and input. It essentially bridges the gap
/// between physical user and the game world via the renderer, and in that sense it's more of a steering wheel than an engine, but
/// `SteeringWheel` makes pretty much no sense whatsoever and "engine" is already within the common vernacular of game developers
///
pub struct Engine {
  world: World,
  state: State,
  ren: Renderer,
  root: console::Root,

  // Debug options the engine tracks
  wizard: bool,
  noclip: bool

}

impl Engine {

  ///
  /// Capture keyboard input from tcod and update player state
  ///
  fn process_keypress(&mut self, keypress: input::Key) {

    match keypress.code {

      // If the keycode isn't escape we continue checking for important keys
      input::KeyCode::Escape => panic!("Bye"),

      // This part of the code is for capturing the keypress not as an object, but as a character for easier parsing
      _ => {

        // We only care if the key is printable, aka, has some symbol attached to it
        if keypress.printable != ' ' {

          // First, make an assumption that the player is affecting their movement as 90% of the game
          // is walking around. We *could* add it to every single one of the vim keypresses to save a trivial ammount of
          // time assigning this variable, but I dislike that.
          let oldpos = self.world.player.actor.pos.clone();

          // In addition, update the game state
          // Important so that if the game state becomes debug we can leave that state instantly
          self.state = State::Keypress;

          // Begin to pattern match the char corresponding to the key pressed
          match keypress.printable {

            // Movement keys are bound to vim-like controls (hjklyubn)
            'h' => {
              self.world.player.actor.move_cart(-1, 0);
              self.world.player.state = Actions::Move;
            },
            'j' => {
              self.world.player.actor.move_cart(0, 1);
              self.world.player.state = Actions::Move;
            },
            'k' => {
              self.world.player.actor.move_cart(0, -1);
              self.world.player.state = Actions::Move;
            },
            'l' => {
              self.world.player.actor.move_cart(1, 0);
              self.world.player.state = Actions::Move;
            },
            'y' => {
              self.world.player.actor.move_cart(-1, -1);
              self.world.player.state = Actions::Move;
            },
            'u' => {
              self.world.player.actor.move_cart(1, -1);
              self.world.player.state = Actions::Move;
            },
            'b' => {
              self.world.player.actor.move_cart(-1, 1);
              self.world.player.state = Actions::Move;
            },
            'n' => {
              self.world.player.actor.move_cart(1, 1);
              self.world.player.state = Actions::Move;
            },

            // Pick up item
            'g' => {
              // Player pos
              let player_pos = self.world.player.actor.pos.clone();
              // Get items at players feet
              // let items_at_feet = self.world.floor.items.iter().filter(|item| item.pos == self.world.player.actor.pos.clone());
              for item in &self.world.floor.items {
                if item.pos == player_pos {
                  match item.property {
                    ItemProperty::Money(ref tender) => self.world.player.wallet += money_value(&tender) * item.quantity as f32
                  }
                  if item.quantity > 1 {
                    log!( (Box::leak(format!("You pick up {} {}s", item.quantity, item.get_id()).into_boxed_str()), item.get_fg()) );
                  } else {
                    log!( (Box::leak(format!("You pick up a {}", item.get_id()).into_boxed_str()), item.get_fg()) );
                  }
                }
              }
              // Prune picked up items
              self.world.floor.items.retain( |item| item.pos != player_pos );
            }

            // Force reload word
            'w' => {
              if self.wizard {
                log!(("You remold the earth like clay." , RGB(255, 0, 0)));
                self.world = World::new(Pos::from_tup(init::map_dimensions()));
              }
              self.state = State::Act(Actions::Unknown);
            },
            // Create an empty level for testing
            'q' => {
              if self.wizard {
                log!(("You empty the universe.", RGB(255, 0, 0)));
                self.world.test_empty();
              }
              self.state = State::Act(Actions::Unknown);
            },
            // Wait
            '.' => {
              self.world.player.state = Actions::Wait;
            },

            // Go downstars (if possible)
            '>' => { self.world.player.state = Actions::DownStair },
            // Go upstairs (if possible)
            '<' => { self.world.player.state = Actions::UpStair },

            // Debug keypresses

            // Toggle scent
            'r' => {
              if self.wizard {
                match self.ren.show_scent {
                  false => log!(("Your eyes percieve scent like light.", RGB(255, 0, 0))),
                  true => log!(("Your vision returns to normal.", RGB(255, 0, 0)))
                }
                self.ren.show_scent = !self.ren.show_scent;
                self.ren.draw_all(&mut self.root, &mut self.world);
              }
              self.state = State::Debug;
            },

            // Toggle sound
            't' => {
              if self.wizard {
                match self.ren.show_sound {
                  false => log!(("Your eyes percieve sound like light.", RGB(255, 0, 0))),
                  true => log!(("Your vision returns to normal.", RGB(255, 0, 0)))
                }
                self.ren.show_sound = !self.ren.show_sound;
                self.ren.draw_all(&mut self.root, &mut self.world);
              }
              self.state = State::Debug;
            },

            // Toggle FoV
            'f' => {
              if self.wizard {
                match self.ren.fov {
                  false => log!(("Your third eye opens, revealing the universe.", RGB(255, 0, 0))),
                  true => log!(("Your third eye closes, concealing the universe in fog.", RGB(255, 0, 0)))
                }
                self.ren.fov = !self.ren.fov;
                self.ren.draw_all(&mut self.root, &mut self.world);
              }
              self.state = State::Debug;
            },

            // Toggle noclip
            'z' => {
              if self.wizard {
                match self.noclip {
                  true => log!(("You form becomes tangible.", RGB(255, 0, 0))),
                  false => log!(("Your form becomes ethereal.", RGB(255, 0, 0)))
                }
                self.noclip = !self.noclip;
              }
              self.state = State::Debug;
            },

            // Tcod test
            'c' => {
              self.ren.tcod_test(&mut self.root);
              self.state = State::Debug;
            },

            // Unbound key, so we just say we don't know what the player did
            _ => { self.world.player.state = Actions::Unknown }

          }

          // Now the game state needs to be properly re-oriented based on the (potential) player action.
          // In addition, we should also process the action of the player while we're here
          match self.state {

            // If state is Debug, don't override
            State::Debug => (),

            _ => {

              // Set game state to player state
              self.state = State::Act(self.world.player.state.clone());

              // Now let's process the player's action
              match self.world.player.state {

                Actions::Move => {

                  // Make sure player doesn't do anything dumb
                  if !self.world.is_valid_pos(self.world.player.actor.pos.x, self.world.player.actor.pos.y) && !self.noclip {
                    self.world.player.actor.pos = oldpos;
                    self.world.player.state = Actions::Unknown;
                  // Otherwise if the position is valid...
                  } else {
                    // Empty for right now
                  }

                }

                _ => ()

              }

            }
          }

        }

        // Prints keycode to console in case if you're trying to find a key that isn't intutive, or you're debugging
        // else {
        //   println!("{:?}", keypress.code);
        // }

      }

    }

  }

  ///
  /// Return a new `Engine`
  ///
  pub fn new() -> Self {

    // Get map height
    let map_dim = Pos::from_tup(init::map_dimensions());

    // Get root console
    let root = init::root();

    Engine {

      world: World::new(map_dim),

      state: State::New,

      ren: Renderer::new(
        map_dim,
        Pos::new(root.width() as isize, root.height() as isize),
        init::console_height(),
        init::panel_width()
      ),

      root: root,

      // Debug
      noclip: false,
      wizard: true

    }

  }

  ///
  /// Update the game state, then update the world depending on the new state
  ///
  fn update(&mut self) {

    match self.state {

      // Player moving or waiting prompts a world update
      State::Act(Actions::Move) | State::Act(Actions::Wait) => self.world.update(),

      // Trying to go up and downstairs prompts the respective response from world
      State::Act(Actions::DownStair) => {
        // No clip through floors
        if self.noclip {
          log!(("You lose your physicality, and sink into the floor.", RGB(255, 150, 150)));
          self.world.go_down();
        } else {
          self.world.player_go_down();
        }
      },

      State::Act(Actions::UpStair) => {
        // No clip through floors
        if self.noclip {
          log!(("You lose your physicality, and ascend through the cieling.", RGB(255, 150, 150)));
          self.world.go_up();
        } else {
          self.world.player_go_up();
        }
      }

      // All other variants are dropped
      _ => {}

    }

  }

  ///
  /// Title screen test
  ///
  fn title_screen(&mut self) {

    // First part of this pretty much just fills the screen with black

    let w = self.root.width().clone();
    let h = self.root.height().clone();

    for x in 0..w {
      for y in 0..h {
        self.root.put_char_ex(
          x as i32,
          y as i32,
          ' ',
          RGB(0, 0, 0).to_tcod(),
          RGB(0, 0, 0).to_tcod()
        );
      }
    }

    // Second part displays stuff

    let title = "Edgequest";
    let subtitle = "Press any key to start.";

    self.root.set_default_foreground(RGB(255, 255, 255).to_tcod());
    // i32 conversion is a pain since I'd rather store stuff as isize and the tcod lib wants i32 since it's
    // pretty much just a C++ interface which is annoying
    self.root.print((w / 2 - (title.len() / 2) as i32) as i32, (h / 2 - 1) as i32, title);
    self.root.print((w / 2 - (subtitle.len() / 2) as i32) as i32, (h / 2 + 1) as i32, subtitle);

    self.root.flush();

    // Wait for keypress
    let keypress = self.root.wait_for_keypress(true);

    // Escape on title should quit the game
    match keypress.code {

      // If the keycode isn't escape we continue checking for important keys
      input::KeyCode::Escape => panic!("Bye"),
      _ => {}

    }

  }

  ///
  /// Play the game.
  ///
  pub fn play(&mut self) {

    // Create the title screen
    self.title_screen();

    // Some starting messages, will be removed in later versions (hopefully)
    log!(("Welcome to Edgequest",                 RGB(255,   0, 255)));
    log!(("Move with vim keys",                   RGB(255, 255, 255)));
    log!(("esc to quit",                          RGB(255, 255, 255)));

    if self.wizard {
      log!(("You are in wizard mode",                   RGB(255,   0,   0)));
      log!(("w to regenerate the map, q to destroy it", RGB(255, 150, 150)));
      log!(("r to toggle scent, t to toggle sound",     RGB(255, 150, 150)));
      log!(("f to toggle FoV, z to toggle noclip",      RGB(255, 150, 150)));
    }

    // Initial update
    self.update();

    // Draw all and capture keypresses
    while !self.root.window_closed() {

      // Draw what the camera sees
      self.ren.draw_all(&mut self.root, &mut self.world);

      // Capture game keys (Keys that change the state of the player)
      // This is what gives it the turn based nature, i.e. waits for player input before
      // doing anything. Process keypress also updates the engine state.
      //
      // We save input to keypress because directly sending the root back into the engine
      // causes it to be mutably borrowed more than once
      let keypress = self.root.wait_for_keypress(true);
      self.process_keypress(keypress);

      // Update engine based on state
      self.update();

    }

  }

}<|endoftext|>


<commit_before>//! Receive incoming Iridium messages through their Direct IP service.
//!
//! Iridium DirectIP is a service provided by the Iridium company. New Mobile
//! Originated messages are forwarded from the Iridium servers to a configured
//! IP address. The Iridum service attempts to initate a TCP connection to port
//! 10800 at the specified IP. If the connection is successful, the MO message
//! is transmitted, then the connection is closed.
//!
//! This module provides a `Server` structure, which can be created to run
//! forever and receive those incoming MO messages.

use std::io;
use std::net::{TcpListener, TcpStream, ToSocketAddrs};
use std::path::Path;
use std::sync::Arc;
use std::thread;

use filesystem::Storage;
use mo::Message;

/// A Iridium DirectIP server.
///
/// The server will listen on a socket address for incoming Iridium SBD Mobile Originated
/// messages. Incoming messages will be stored using `sbd::filesystem::Storage`. Errors are logged
/// using the logging framework.
#[allow(missing_copy_implementations)]
#[derive(Debug)]
pub struct Server<A: ToSocketAddrs + Sync> {
    addr: A,
    listener: Option<TcpListener>,
    storage: Arc<Storage>,
}

impl<A: ToSocketAddrs + Sync> Server<A> {
    /// Creates a new server that will listen on `addr` and write messages to `root`.
    ///
    /// This method does not actually bind to the socket address or do anything with the root
    /// directory. Use `bind` and `serve_forever` to actually do stuff.
    ///
    /// The `root` parameters is used to create a new `sbd::filesystem::Storage`, and the server
    /// will store all incoming messages under `root`.
    ///
    /// # Examples
    ///
    /// ```
    /// let server = sbd::directip::Server::new("0.0.0.0:10800", "/var/iridium");
    /// ```
    pub fn new<P: AsRef<Path>>(addr: A, root: P) -> Server<A> {
        Server {
            addr: addr,
            listener: None,
            storage: Arc::new(Storage::new(root)),
        }
    }

    /// Binds this server to its tcp socket.
    ///
    /// This is a seperate operation from `serve_forever` so that we can capture any errors
    /// associated with the underlying `TcpListener::bind`.
    ///
    /// # Examples
    ///
    /// ```
    /// let mut server = sbd::directip::Server::new("0.0.0.0:10800", "/var/iridium");
    /// server.bind().unwrap();
    /// ```
    pub fn bind(&mut self) -> io::Result<()> {
        self.listener = Some(try!(self.create_listener()));
        Ok(())
    }

    /// Starts the DirectIP server and serves forever.
    ///
    /// # Panics
    ///
    /// This method panics if it has a problem binding to the tcp socket address. To avoid a panic,
    /// use `Server::bind` before calling `Server::serve_forever`.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// let mut server = sbd::directip::Server::new("0.0.0.0:10800", "/var/iridium");
    /// server.bind().unwrap();
    /// server.serve_forever();
    /// ```
    pub fn serve_forever(&mut self) {
        let listener = match self.listener {
            Some(ref listener) => listener,
            None => {
                self.listener = Some(self.create_listener().unwrap());
                self.listener.as_ref().unwrap()
            }
        };
        for stream in listener.incoming() {
            match stream {
                Ok(stream) => {
                    let storage = self.storage.clone();
                    thread::spawn(move || handle_stream(stream, storage));
                }
                Err(err) => {
                    thread::spawn(move || handle_error(err));
                }
            }
        }
    }

    fn create_listener(&self) -> io::Result<TcpListener> {
        TcpListener::bind(&self.addr)
    }
}

/// Handles an incoming DirectIP stream.
fn handle_stream(stream: TcpStream, storage: Arc<Storage>) {
    match stream.peer_addr() {
        Ok(addr) => {
            debug!("Handling TcpStream from {}", addr);
        }
        Err(err) => {
            warn!("Problem when extracting peer address from TcpStream, but we'll press on: {:?}",
                  err);
        }
    }
    let ref message = match Message::read_from(stream) {
        Ok(message) => {
            info!("Recieved message with {} byte payload",
                  message.payload_ref().len());
            message
        }
        Err(err) => {
            error!("Error when reading message: {:?}", err);
            return;
        }
    };
    match storage.store(message) {
        Ok(path) => info!("Stored message to {:?}", path),
        Err(err) => error!("Problem storing message: {:?}", err),
    }
}

/// Handles an error when handling a connection.
fn handle_error(err: io::Error) {
    error!("Error when receiving tcp communication: {:?}", err);
}
<commit_msg>Serve forever should consume the server<commit_after>//! Receive incoming Iridium messages through their Direct IP service.
//!
//! Iridium DirectIP is a service provided by the Iridium company. New Mobile
//! Originated messages are forwarded from the Iridium servers to a configured
//! IP address. The Iridum service attempts to initate a TCP connection to port
//! 10800 at the specified IP. If the connection is successful, the MO message
//! is transmitted, then the connection is closed.
//!
//! This module provides a `Server` structure, which can be created to run
//! forever and receive those incoming MO messages.

use std::io;
use std::net::{TcpListener, TcpStream, ToSocketAddrs};
use std::path::Path;
use std::sync::Arc;
use std::thread;

use filesystem::Storage;
use mo::Message;

/// A Iridium DirectIP server.
///
/// The server will listen on a socket address for incoming Iridium SBD Mobile Originated
/// messages. Incoming messages will be stored using `sbd::filesystem::Storage`. Errors are logged
/// using the logging framework.
#[allow(missing_copy_implementations)]
#[derive(Debug)]
pub struct Server<A: ToSocketAddrs + Sync> {
    addr: A,
    listener: Option<TcpListener>,
    storage: Arc<Storage>,
}

impl<A: ToSocketAddrs + Sync> Server<A> {
    /// Creates a new server that will listen on `addr` and write messages to `root`.
    ///
    /// This method does not actually bind to the socket address or do anything with the root
    /// directory. Use `bind` and `serve_forever` to actually do stuff.
    ///
    /// The `root` parameters is used to create a new `sbd::filesystem::Storage`, and the server
    /// will store all incoming messages under `root`.
    ///
    /// # Examples
    ///
    /// ```
    /// let server = sbd::directip::Server::new("0.0.0.0:10800", "/var/iridium");
    /// ```
    pub fn new<P: AsRef<Path>>(addr: A, root: P) -> Server<A> {
        Server {
            addr: addr,
            listener: None,
            storage: Arc::new(Storage::new(root)),
        }
    }

    /// Binds this server to its tcp socket.
    ///
    /// This is a seperate operation from `serve_forever` so that we can capture any errors
    /// associated with the underlying `TcpListener::bind`.
    ///
    /// # Examples
    ///
    /// ```
    /// let mut server = sbd::directip::Server::new("0.0.0.0:10800", "/var/iridium");
    /// server.bind().unwrap();
    /// ```
    pub fn bind(&mut self) -> io::Result<()> {
        self.listener = Some(try!(self.create_listener()));
        Ok(())
    }

    /// Starts the DirectIP server and serves forever.
    ///
    /// # Panics
    ///
    /// This method panics if it has a problem binding to the tcp socket address. To avoid a panic,
    /// use `Server::bind` before calling `Server::serve_forever`.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// let mut server = sbd::directip::Server::new("0.0.0.0:10800", "/var/iridium");
    /// server.bind().unwrap();
    /// server.serve_forever();
    /// ```
    pub fn serve_forever(mut self) {
        let listener = match self.listener {
            Some(ref listener) => listener,
            None => {
                self.listener = Some(self.create_listener().unwrap());
                self.listener.as_ref().unwrap()
            }
        };
        for stream in listener.incoming() {
            match stream {
                Ok(stream) => {
                    let storage = self.storage.clone();
                    thread::spawn(move || handle_stream(stream, storage));
                }
                Err(err) => {
                    thread::spawn(move || handle_error(err));
                }
            }
        }
    }

    fn create_listener(&self) -> io::Result<TcpListener> {
        TcpListener::bind(&self.addr)
    }
}

/// Handles an incoming DirectIP stream.
fn handle_stream(stream: TcpStream, storage: Arc<Storage>) {
    match stream.peer_addr() {
        Ok(addr) => {
            debug!("Handling TcpStream from {}", addr);
        }
        Err(err) => {
            warn!("Problem when extracting peer address from TcpStream, but we'll press on: {:?}",
                  err);
        }
    }
    let ref message = match Message::read_from(stream) {
        Ok(message) => {
            info!("Recieved message with {} byte payload",
                  message.payload_ref().len());
            message
        }
        Err(err) => {
            error!("Error when reading message: {:?}", err);
            return;
        }
    };
    match storage.store(message) {
        Ok(path) => info!("Stored message to {:?}", path),
        Err(err) => error!("Problem storing message: {:?}", err),
    }
}

/// Handles an error when handling a connection.
fn handle_error(err: io::Error) {
    error!("Error when receiving tcp communication: {:?}", err);
}
<|endoftext|>