<commit_before>//! FIXME: write short doc here

use std::{convert::TryFrom, env, ops, path::Path, process::Command};

use anyhow::{bail, format_err, Result};
use paths::{AbsPath, AbsPathBuf};
use ra_arena::{Arena, Idx};

use crate::utf8_stdout;

#[derive(Default, Debug, Clone, Eq, PartialEq)]
pub struct Sysroot {
    crates: Arena<SysrootCrateData>,
}

pub type SysrootCrate = Idx<SysrootCrateData>;

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct SysrootCrateData {
    pub name: String,
    pub root: AbsPathBuf,
    pub deps: Vec<SysrootCrate>,
}

impl ops::Index<SysrootCrate> for Sysroot {
    type Output = SysrootCrateData;
    fn index(&self, index: SysrootCrate) -> &SysrootCrateData {
        &self.crates[index]
    }
}

impl Sysroot {
    pub fn core(&self) -> Option<SysrootCrate> {
        self.by_name("core")
    }

    pub fn alloc(&self) -> Option<SysrootCrate> {
        self.by_name("alloc")
    }

    pub fn std(&self) -> Option<SysrootCrate> {
        self.by_name("std")
    }

    pub fn proc_macro(&self) -> Option<SysrootCrate> {
        self.by_name("proc_macro")
    }

    pub fn crates<'a>(&'a self) -> impl Iterator<Item = SysrootCrate> + ExactSizeIterator + 'a {
        self.crates.iter().map(|(id, _data)| id)
    }

    pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> {
        let src = get_or_install_rust_src(cargo_toml)?;
        let mut sysroot = Sysroot { crates: Arena::default() };
        for name in SYSROOT_CRATES.trim().lines() {
            let root = src.join(format!("lib{}", name)).join("lib.rs");
            if root.exists() {
                sysroot.crates.alloc(SysrootCrateData {
                    name: name.into(),
                    root,
                    deps: Vec::new(),
                });
            }
        }
        if let Some(std) = sysroot.std() {
            for dep in STD_DEPS.trim().lines() {
                if let Some(dep) = sysroot.by_name(dep) {
                    sysroot.crates[std].deps.push(dep)
                }
            }
        }
        if let Some(alloc) = sysroot.alloc() {
            if let Some(core) = sysroot.core() {
                sysroot.crates[alloc].deps.push(core);
            }
        }
        Ok(sysroot)
    }

    fn by_name(&self, name: &str) -> Option<SysrootCrate> {
        self.crates.iter().find(|(_id, data)| data.name == name).map(|(id, _data)| id)
    }
}

fn get_or_install_rust_src(cargo_toml: &AbsPath) -> Result<AbsPathBuf> {
    if let Ok(path) = env::var("RUST_SRC_PATH") {
        let path = AbsPathBuf::try_from(path.as_str())
            .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?;
        return Ok(path);
    }
    let current_dir = cargo_toml.parent().unwrap();
    let mut rustc = Command::new(ra_toolchain::rustc());
    rustc.current_dir(current_dir).args(&["--print", "sysroot"]);
    let stdout = utf8_stdout(rustc)?;
    let sysroot_path = AbsPath::assert(Path::new(stdout.trim()));
    let src_path = sysroot_path.join("lib/rustlib/src/rust/src");

    if !src_path.exists() {
        let mut rustup = Command::new(ra_toolchain::rustup());
        rustup.current_dir(current_dir).args(&["component", "add", "rust-src"]);
        utf8_stdout(rustup)?;
    }
    if !src_path.exists() {
        bail!(
            "can't load standard library from sysroot\n\
            {}\n\
            (discovered via `rustc --print sysroot`)\n\
            try running `rustup component add rust-src` or set `RUST_SRC_PATH`",
            src_path.display(),
        )
    }
    Ok(src_path)
}

impl SysrootCrateData {
    pub fn root_dir(&self) -> &AbsPath {
        self.root.parent().unwrap()
    }
}

const SYSROOT_CRATES: &str = "
std
core
alloc
collections
libc
proc_macro
rustc_unicode
std_unicode
test
alloc_jemalloc
alloc_system
compiler_builtins
getopts
panic_unwind
panic_abort
rand
term
unwind
build_helper
rustc_asan
rustc_lsan
rustc_msan
rustc_tsan
syntax";

const STD_DEPS: &str = "
alloc
alloc_jemalloc
alloc_system
core
panic_abort
rand
compiler_builtins
unwind
rustc_asan
rustc_lsan
rustc_msan
rustc_tsan
build_helper";
<commit_msg>Add workaround for changing sysroot paths<commit_after>//! FIXME: write short doc here

use std::{convert::TryFrom, env, ops, path::Path, process::Command};

use anyhow::{bail, format_err, Result};
use paths::{AbsPath, AbsPathBuf};
use ra_arena::{Arena, Idx};

use crate::utf8_stdout;

#[derive(Default, Debug, Clone, Eq, PartialEq)]
pub struct Sysroot {
    crates: Arena<SysrootCrateData>,
}

pub type SysrootCrate = Idx<SysrootCrateData>;

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct SysrootCrateData {
    pub name: String,
    pub root: AbsPathBuf,
    pub deps: Vec<SysrootCrate>,
}

impl ops::Index<SysrootCrate> for Sysroot {
    type Output = SysrootCrateData;
    fn index(&self, index: SysrootCrate) -> &SysrootCrateData {
        &self.crates[index]
    }
}

impl Sysroot {
    pub fn core(&self) -> Option<SysrootCrate> {
        self.by_name("core")
    }

    pub fn alloc(&self) -> Option<SysrootCrate> {
        self.by_name("alloc")
    }

    pub fn std(&self) -> Option<SysrootCrate> {
        self.by_name("std")
    }

    pub fn proc_macro(&self) -> Option<SysrootCrate> {
        self.by_name("proc_macro")
    }

    pub fn crates<'a>(&'a self) -> impl Iterator<Item = SysrootCrate> + ExactSizeIterator + 'a {
        self.crates.iter().map(|(id, _data)| id)
    }

    pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> {
        let src = get_or_install_rust_src(cargo_toml)?;
        let mut sysroot = Sysroot { crates: Arena::default() };
        for name in SYSROOT_CRATES.trim().lines() {
            // FIXME: remove this path when 1.47 comes out
            // https://github.com/rust-lang/rust/pull/73265
            let root = src.join(format!("lib{}", name)).join("lib.rs");
            if root.exists() {
                sysroot.crates.alloc(SysrootCrateData {
                    name: name.into(),
                    root,
                    deps: Vec::new(),
                });
            } else {
                let root = src.join(name).join("src/lib.rs");
                if root.exists() {
                    sysroot.crates.alloc(SysrootCrateData {
                        name: name.into(),
                        root,
                        deps: Vec::new(),
                    });
                }
            }
        }
        if let Some(std) = sysroot.std() {
            for dep in STD_DEPS.trim().lines() {
                if let Some(dep) = sysroot.by_name(dep) {
                    sysroot.crates[std].deps.push(dep)
                }
            }
        }
        if let Some(alloc) = sysroot.alloc() {
            if let Some(core) = sysroot.core() {
                sysroot.crates[alloc].deps.push(core);
            }
        }
        Ok(sysroot)
    }

    fn by_name(&self, name: &str) -> Option<SysrootCrate> {
        self.crates.iter().find(|(_id, data)| data.name == name).map(|(id, _data)| id)
    }
}

fn get_or_install_rust_src(cargo_toml: &AbsPath) -> Result<AbsPathBuf> {
    if let Ok(path) = env::var("RUST_SRC_PATH") {
        let path = AbsPathBuf::try_from(path.as_str())
            .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?;
        return Ok(path);
    }
    let current_dir = cargo_toml.parent().unwrap();
    let mut rustc = Command::new(ra_toolchain::rustc());
    rustc.current_dir(current_dir).args(&["--print", "sysroot"]);
    let stdout = utf8_stdout(rustc)?;
    let sysroot_path = AbsPath::assert(Path::new(stdout.trim()));
    let mut src = get_rust_src(sysroot_path);
    if src.is_none() {
        let mut rustup = Command::new(ra_toolchain::rustup());
        rustup.current_dir(current_dir).args(&["component", "add", "rust-src"]);
        utf8_stdout(rustup)?;
        src = get_rust_src(sysroot_path);
    }
    match src {
        Some(r) => Ok(r),
        None => bail!(
            "can't load standard library from sysroot\n\
            {}\n\
            (discovered via `rustc --print sysroot`)\n\
            try running `rustup component add rust-src` or set `RUST_SRC_PATH`",
            sysroot_path.display(),
        ),
    }
}

fn get_rust_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> {
    // try the new path first since the old one still exists
    let mut src_path = sysroot_path.join("lib/rustlib/src/rust/library");
    if !src_path.exists() {
        // FIXME: remove this path when 1.47 comes out
        // https://github.com/rust-lang/rust/pull/73265
        src_path = sysroot_path.join("lib/rustlib/src/rust/src");
    }
    if src_path.exists() {
        Some(src_path)
    } else {
        None
    }
}

impl SysrootCrateData {
    pub fn root_dir(&self) -> &AbsPath {
        self.root.parent().unwrap()
    }
}

const SYSROOT_CRATES: &str = "
std
core
alloc
collections
libc
proc_macro
rustc_unicode
std_unicode
test
alloc_jemalloc
alloc_system
compiler_builtins
getopts
panic_unwind
panic_abort
rand
term
unwind
build_helper
rustc_asan
rustc_lsan
rustc_msan
rustc_tsan
syntax";

const STD_DEPS: &str = "
alloc
alloc_jemalloc
alloc_system
core
panic_abort
rand
compiler_builtins
unwind
rustc_asan
rustc_lsan
rustc_msan
rustc_tsan
build_helper";
<|endoftext|>


<commit_before>// Copyright 2017 The xi-editor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Representation and calculation of movement within a view.

use std::cmp::max;

use selection::{HorizPos, SelRegion, Selection};
use view::View;
use word_boundaries::WordCursor;
use xi_rope::{Cursor, LinesMetric, Rope};

/// The specification of a movement.
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Movement {
    /// Move to the left by one grapheme cluster.
    Left,
    /// Move to the right by one grapheme cluster.
    Right,
    /// Move to the left by one word.
    LeftWord,
    /// Move to the right by one word.
    RightWord,
    /// Move to left end of visible line.
    LeftOfLine,
    /// Move to right end of visible line.
    RightOfLine,
    /// Move up one visible line.
    Up,
    /// Move down one visible line.
    Down,
    /// Move up one viewport height.
    UpPage,
    /// Move down one viewport height.
    DownPage,
    /// Move up to the next line that can preserve the cursor position.
    UpEnforceHorizPos,
    /// Move down to the next line that can preserve the cursor position.
    DownEnforceHorizPos,
    /// Move to the start of the text line.
    StartOfParagraph,
    /// Move to the end of the text line.
    EndOfParagraph,
    /// Move to the end of the text line, or next line if already at end.
    EndOfParagraphKill,
    /// Move to the start of the document.
    StartOfDocument,
    /// Move to the end of the document
    EndOfDocument,
}

/// Compute movement based on vertical motion by the given number of lines.
///
/// Note: in non-exceptional cases, this function preserves the `horiz`
/// field of the selection region.
fn vertical_motion(
    r: SelRegion,
    view: &View,
    text: &Rope,
    line_delta: isize,
    modify: bool,
) -> (usize, Option<HorizPos>) {
    // The active point of the selection
    let active = if modify {
        r.end
    } else if line_delta < 0 {
        r.min()
    } else {
        r.max()
    };
    let col = if let Some(col) = r.horiz { col } else { view.offset_to_line_col(text, active).1 };
    // This code is quite careful to avoid integer overflow.
    // TODO: write tests to verify
    let line = view.line_of_offset(text, active);
    if line_delta < 0 && (-line_delta as usize) > line {
        if enforce_horiz_pos {
            return (active, Some(col));
        } else {
            return (0, Some(col));
        }
    }
    let mut line = if line_delta < 0 {
        line - (-line_delta as usize)
    } else {
        line.saturating_add(line_delta as usize)
    };
    let n_lines = view.line_of_offset(text, text.len());

    if !enforce_horiz_pos {
        if line > n_lines {
            return (text.len(), Some(col));
        }

        return (view.line_col_to_offset(text, line, col), Some(col));
    }

    loop {
        // If the line is longer than the current cursor position, break.
        let line_length = view.offset_of_line(text, line.saturating_add(1)) - view.offset_of_line(text, line);
        if line_length > col { break; }

        // If you are trying to add a selection past the end of the file or before the first line, return original selection
        if line >= n_lines || (line == 0 && line_delta < 0) {
            return (active, Some(col));
        }

        line = if line_delta < 0 { line - 1 } else { line.saturating_add(1) };
    }

    (view.line_col_to_offset(text, line, col), Some(col))
}

/// When paging through a file, the number of lines from the previous page
/// that will also be visible in the next.
const SCROLL_OVERLAP: isize = 2;

/// Computes the actual desired amount of scrolling (generally slightly
/// less than the height of the viewport, to allow overlap).
fn scroll_height(view: &View) -> isize {
    max(view.scroll_height() as isize - SCROLL_OVERLAP, 1)
}

/// Compute the result of movement on one selection region.
pub fn region_movement(
    m: Movement,
    r: SelRegion,
    view: &View,
    text: &Rope,
    modify: bool,
) -> SelRegion {
    let (offset, horiz) = match m {
        Movement::Left => {
            if r.is_caret() || modify {
                if let Some(offset) = text.prev_grapheme_offset(r.end) {
                    (offset, None)
                } else {
                    (0, r.horiz)
                }
            } else {
                (r.min(), None)
            }
        }
        Movement::Right => {
            if r.is_caret() || modify {
                if let Some(offset) = text.next_grapheme_offset(r.end) {
                    (offset, None)
                } else {
                    (r.end, r.horiz)
                }
            } else {
                (r.max(), None)
            }
        }
        Movement::LeftWord => {
            let mut word_cursor = WordCursor::new(text, r.end);
            let offset = word_cursor.prev_boundary().unwrap_or(0);
            (offset, None)
        }
        Movement::RightWord => {
            let mut word_cursor = WordCursor::new(text, r.end);
            let offset = word_cursor.next_boundary().unwrap_or_else(|| text.len());
            (offset, None)
        }
        Movement::LeftOfLine => {
            let line = view.line_of_offset(text, r.end);
            let offset = view.offset_of_line(text, line);
            (offset, None)
        }
        Movement::RightOfLine => {
            let line = view.line_of_offset(text, r.end);
            let mut offset = text.len();

            // calculate end of line
            let next_line_offset = view.offset_of_line(text, line + 1);
            if line < view.line_of_offset(text, offset) {
                if let Some(prev) = text.prev_grapheme_offset(next_line_offset) {
                    offset = prev;
                }
            }
            (offset, None)
        }
        Movement::Up => vertical_motion(r, view, text, -1, modify, false),
        Movement::Down => vertical_motion(r, view, text, 1, modify, false),
        Movement::UpEnforceHorizPos => vertical_motion(r, view, text, -1, modify, true),
        Movement::DownEnforceHorizPos => vertical_motion(r, view, text, 1, modify, true),
        Movement::StartOfParagraph => {
            // Note: TextEdit would start at modify ? r.end : r.min()
            let mut cursor = Cursor::new(&text, r.end);
            let offset = cursor.prev::<LinesMetric>().unwrap_or(0);
            (offset, None)
        }
        Movement::EndOfParagraph => {
            // Note: TextEdit would start at modify ? r.end : r.max()
            let mut offset = r.end;
            let mut cursor = Cursor::new(&text, offset);
            if let Some(next_para_offset) = cursor.next::<LinesMetric>() {
                if cursor.is_boundary::<LinesMetric>() {
                    if let Some(eol) = text.prev_grapheme_offset(next_para_offset) {
                        offset = eol;
                    }
                } else if cursor.pos() == text.len() {
                    offset = text.len();
                }
            }
            (offset, None)
        }
        Movement::EndOfParagraphKill => {
            // Note: TextEdit would start at modify ? r.end : r.max()
            let mut offset = r.end;
            let mut cursor = Cursor::new(&text, offset);
            if let Some(next_para_offset) = cursor.next::<LinesMetric>() {
                offset = next_para_offset;
                if cursor.is_boundary::<LinesMetric>() {
                    if let Some(eol) = text.prev_grapheme_offset(next_para_offset) {
                        if eol != r.end {
                            offset = eol;
                        }
                    }
                }
            }
            (offset, None)
        }
        Movement::UpPage => vertical_motion(r, view, text, -scroll_height(view), modify, false),
        Movement::DownPage => vertical_motion(r, view, text, scroll_height(view), modify, false),
        Movement::StartOfDocument => (0, None),
        Movement::EndOfDocument => (text.len(), None),
    };
    SelRegion::new(if modify { r.start } else { offset }, offset).with_horiz(horiz)
}

/// Compute a new selection by applying a movement to an existing selection.
///
/// In a multi-region selection, this function applies the movement to each
/// region in the selection, and returns the union of the results.
///
/// If `modify` is `true`, the selections are modified, otherwise the results
/// of individual region movements become carets.
pub fn selection_movement(
    m: Movement,
    s: &Selection,
    view: &View,
    text: &Rope,
    modify: bool,
) -> Selection {
    let mut result = Selection::new();
    for &r in s.iter() {
        let new_region = region_movement(m, r, view, text, modify);
        result.add_region(new_region);
    }
    result
}
<commit_msg>Improved comments and uses current cursor position.<commit_after>// Copyright 2017 The xi-editor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Representation and calculation of movement within a view.

use std::cmp::max;

use selection::{HorizPos, SelRegion, Selection};
use view::View;
use word_boundaries::WordCursor;
use xi_rope::{Cursor, LinesMetric, Rope};

/// The specification of a movement.
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Movement {
    /// Move to the left by one grapheme cluster.
    Left,
    /// Move to the right by one grapheme cluster.
    Right,
    /// Move to the left by one word.
    LeftWord,
    /// Move to the right by one word.
    RightWord,
    /// Move to left end of visible line.
    LeftOfLine,
    /// Move to right end of visible line.
    RightOfLine,
    /// Move up one visible line.
    Up,
    /// Move down one visible line.
    Down,
    /// Move up one viewport height.
    UpPage,
    /// Move down one viewport height.
    DownPage,
    /// Move up to the next line that can preserve the cursor position.
    UpEnforceHorizPos,
    /// Move down to the next line that can preserve the cursor position.
    DownEnforceHorizPos,
    /// Move to the start of the text line.
    StartOfParagraph,
    /// Move to the end of the text line.
    EndOfParagraph,
    /// Move to the end of the text line, or next line if already at end.
    EndOfParagraphKill,
    /// Move to the start of the document.
    StartOfDocument,
    /// Move to the end of the document
    EndOfDocument,
}

/// Compute movement based on vertical motion by the given number of lines.
///
/// Note: in non-exceptional cases, this function preserves the `horiz`
/// field of the selection region.
fn vertical_motion(
    r: SelRegion,
    view: &View,
    text: &Rope,
    line_delta: isize,
    modify: bool,
) -> (usize, Option<HorizPos>) {
    // The active point of the selection
    let active = if modify {
        r.end
    } else if line_delta < 0 {
        r.min()
    } else {
        r.max()
    };
    let col = if let Some(col) = r.horiz { col } else { view.offset_to_line_col(text, active).1 };
    // This code is quite careful to avoid integer overflow.
    // TODO: write tests to verify
    let line = view.line_of_offset(text, active);
    // Line Length include the newline, we want the non newline count.
    let mut line_length = view.offset_of_line(text, line.saturating_add(1)) - view.offset_of_line(text, line) - 1;
    if line_delta < 0 && (-line_delta as usize) > line {
        if enforce_horiz_pos {
            return (active, Some(col));
        } else {
            return (0, Some(col));
        }
    }
    let mut line = if line_delta < 0 {
        line - (-line_delta as usize)
    } else {
        line.saturating_add(line_delta as usize)
    };
    let n_lines = view.line_of_offset(text, text.len());

    if !enforce_horiz_pos {
        if line > n_lines {
            return (text.len(), Some(col));
        }

        return (view.line_col_to_offset(text, line, col), Some(col));
    }

    // If the active columns is longer than the current line, use the current line length.
    let col = if line_length < col { line_length } else { col };

    loop {
        line_length = view.offset_of_line(text, line.saturating_add(1)) - view.offset_of_line(text, line);

        // If the line is longer than the current cursor position, break.
        // We use > instead of >= because line_length includes newline.
        if line_length > col { break; }

        // If you are trying to add a selection past the end of the file or before the first line, return original selection
        if line >= n_lines || (line == 0 && line_delta < 0) {
            return (active, Some(col));
        }

        line = if line_delta < 0 { line - 1 } else { line.saturating_add(1) };
    }

    (view.line_col_to_offset(text, line, col), Some(col))
}

/// When paging through a file, the number of lines from the previous page
/// that will also be visible in the next.
const SCROLL_OVERLAP: isize = 2;

/// Computes the actual desired amount of scrolling (generally slightly
/// less than the height of the viewport, to allow overlap).
fn scroll_height(view: &View) -> isize {
    max(view.scroll_height() as isize - SCROLL_OVERLAP, 1)
}

/// Compute the result of movement on one selection region.
pub fn region_movement(
    m: Movement,
    r: SelRegion,
    view: &View,
    text: &Rope,
    modify: bool,
) -> SelRegion {
    let (offset, horiz) = match m {
        Movement::Left => {
            if r.is_caret() || modify {
                if let Some(offset) = text.prev_grapheme_offset(r.end) {
                    (offset, None)
                } else {
                    (0, r.horiz)
                }
            } else {
                (r.min(), None)
            }
        }
        Movement::Right => {
            if r.is_caret() || modify {
                if let Some(offset) = text.next_grapheme_offset(r.end) {
                    (offset, None)
                } else {
                    (r.end, r.horiz)
                }
            } else {
                (r.max(), None)
            }
        }
        Movement::LeftWord => {
            let mut word_cursor = WordCursor::new(text, r.end);
            let offset = word_cursor.prev_boundary().unwrap_or(0);
            (offset, None)
        }
        Movement::RightWord => {
            let mut word_cursor = WordCursor::new(text, r.end);
            let offset = word_cursor.next_boundary().unwrap_or_else(|| text.len());
            (offset, None)
        }
        Movement::LeftOfLine => {
            let line = view.line_of_offset(text, r.end);
            let offset = view.offset_of_line(text, line);
            (offset, None)
        }
        Movement::RightOfLine => {
            let line = view.line_of_offset(text, r.end);
            let mut offset = text.len();

            // calculate end of line
            let next_line_offset = view.offset_of_line(text, line + 1);
            if line < view.line_of_offset(text, offset) {
                if let Some(prev) = text.prev_grapheme_offset(next_line_offset) {
                    offset = prev;
                }
            }
            (offset, None)
        }
        Movement::Up => vertical_motion(r, view, text, -1, modify, false),
        Movement::Down => vertical_motion(r, view, text, 1, modify, false),
        Movement::UpEnforceHorizPos => vertical_motion(r, view, text, -1, modify, true),
        Movement::DownEnforceHorizPos => vertical_motion(r, view, text, 1, modify, true),
        Movement::StartOfParagraph => {
            // Note: TextEdit would start at modify ? r.end : r.min()
            let mut cursor = Cursor::new(&text, r.end);
            let offset = cursor.prev::<LinesMetric>().unwrap_or(0);
            (offset, None)
        }
        Movement::EndOfParagraph => {
            // Note: TextEdit would start at modify ? r.end : r.max()
            let mut offset = r.end;
            let mut cursor = Cursor::new(&text, offset);
            if let Some(next_para_offset) = cursor.next::<LinesMetric>() {
                if cursor.is_boundary::<LinesMetric>() {
                    if let Some(eol) = text.prev_grapheme_offset(next_para_offset) {
                        offset = eol;
                    }
                } else if cursor.pos() == text.len() {
                    offset = text.len();
                }
            }
            (offset, None)
        }
        Movement::EndOfParagraphKill => {
            // Note: TextEdit would start at modify ? r.end : r.max()
            let mut offset = r.end;
            let mut cursor = Cursor::new(&text, offset);
            if let Some(next_para_offset) = cursor.next::<LinesMetric>() {
                offset = next_para_offset;
                if cursor.is_boundary::<LinesMetric>() {
                    if let Some(eol) = text.prev_grapheme_offset(next_para_offset) {
                        if eol != r.end {
                            offset = eol;
                        }
                    }
                }
            }
            (offset, None)
        }
        Movement::UpPage => vertical_motion(r, view, text, -scroll_height(view), modify, false),
        Movement::DownPage => vertical_motion(r, view, text, scroll_height(view), modify, false),
        Movement::StartOfDocument => (0, None),
        Movement::EndOfDocument => (text.len(), None),
    };
    SelRegion::new(if modify { r.start } else { offset }, offset).with_horiz(horiz)
}

/// Compute a new selection by applying a movement to an existing selection.
///
/// In a multi-region selection, this function applies the movement to each
/// region in the selection, and returns the union of the results.
///
/// If `modify` is `true`, the selections are modified, otherwise the results
/// of individual region movements become carets.
pub fn selection_movement(
    m: Movement,
    s: &Selection,
    view: &View,
    text: &Rope,
    modify: bool,
) -> Selection {
    let mut result = Selection::new();
    for &r in s.iter() {
        let new_region = region_movement(m, r, view, text, modify);
        result.add_region(new_region);
    }
    result
}
<|endoftext|>


<commit_before>use std::collections::HashSet;

struct WorldMap{
    map: HashSet<(i8, i8)>
}

fn get_neighbours_coordinates(cell_coordinates: (i8,i8)) -> HashSet<(i8,i8)> {
    let mut neighbour_coordinates = HashSet::new();
    let (x, y) = cell_coordinates;
    neighbour_coordinates.insert((x-1, y-1));
    neighbour_coordinates.insert((x-1, y));
    neighbour_coordinates.insert((x-1, y+1));
    neighbour_coordinates.insert((x, y-1));
    neighbour_coordinates.insert((x, y+1));
    neighbour_coordinates.insert((x+1, y-1));
    neighbour_coordinates.insert((x+1, y));
    neighbour_coordinates.insert((x+1, y+1));
    return neighbour_coordinates;
}

impl WorldMap{

    fn empty() -> WorldMap {
        return WorldMap::new( HashSet::new() );
    }

    fn new(new_map: HashSet<(i8,i8)>) -> WorldMap {
        return WorldMap { map: new_map };
    }

    fn mark_cell_alive(&self, cell_coordinates: (i8, i8)) -> WorldMap {
        let mut new_map = self.map.clone();
        new_map.insert(cell_coordinates);
        let new_world = WorldMap::new(new_map);
        return new_world;
    }

    fn is_cell_alive(&self, cell_coordinates: (i8,i8)) -> bool {
        return self.map.contains(&cell_coordinates);
    }

    fn count_alive_neighbours(&self, cell_coordinates: (i8, i8)) -> usize {
        let neighbour_coordinates = get_neighbours_coordinates(cell_coordinates);
        let neighbours_alive = self.map.intersection(&neighbour_coordinates).collect::<Vec<&(i8,i8)>>();
        return neighbours_alive.len();
    }

    fn should_live(&self, cell_coordinates: (i8,i8)) -> bool {
        let alive_neighbours_count = self.count_alive_neighbours(cell_coordinates);
        let result = match alive_neighbours_count {
            0...1 => false,
            2...3 => true,
            4...8 => false,
            _ => panic!("wrong number of neighbours: {}", alive_neighbours_count)
        };
        return result;
    }
}

#[test]
// rule 1
fn alive_cell_with_no_alive_neighbours_should_die() {
    let coords = (0,0);
    let world = WorldMap::empty();
    let new_world = world.mark_cell_alive(coords);
    let should_die = !new_world.should_live(coords);
    assert!(should_die);
}

#[test]
// rule 1
fn alive_cell_with_one_alive_neighbour_should_die() {
    let (x, y) = (0,0);
    let coords = (x,y);
    let world = WorldMap::empty();
    let new_world = world.
        mark_cell_alive(coords).
        mark_cell_alive((x, y+1));
    let should_die = !new_world.should_live(coords);
    assert!(should_die);
}

#[test]
// rule 2
fn alive_cell_with_two_alive_neighbours_should_stay_alive() {
    let (x, y) = (0,0);
    let coords = (x,y);
    let world = WorldMap::empty();
    let new_world = world.
        mark_cell_alive(coords).
        mark_cell_alive((x, y+1)).
        mark_cell_alive((x-1, y+1));
    let should_live = new_world.should_live(coords);
    assert!(should_live);
}

#[test]
// rule 2
fn alive_cell_with_three_alive_neighbours_should_stay_alive() {
    let (x, y) = (0,0);
    let coords = (x,y);
    let world = WorldMap::empty();
    let new_world = world.
        mark_cell_alive(coords).
        mark_cell_alive((x, y+1)).
        mark_cell_alive((x-1, y)).
        mark_cell_alive((x-1, y+1));
    let should_live = new_world.should_live(coords);
    assert!(should_live);
}

#[test]
// rule 3
fn alive_cell_with_four_alive_neighbours_should_die() {
    let (x, y) = (0,0);
    let coords = (x,y);
    let world = WorldMap::empty();
    let new_world = world.
        mark_cell_alive(coords).
        mark_cell_alive((x, y+1)).
        mark_cell_alive((x-1, y)).
        mark_cell_alive((x-1, y+1)).
        mark_cell_alive((x+1, y+1));
    let should_die = !new_world.should_live(coords);
    assert!(should_die);
}

#[test]
fn the_only_alive_cell_should_have_no_alive_neighbours() {
    let coords = (0,0);
    let world = WorldMap::empty();
    let new_world = world.mark_cell_alive(coords);
    let alive_neighbours_count = new_world.count_alive_neighbours(coords);
    assert_eq!(0, alive_neighbours_count);
}

#[test]
fn alive_neighbour_on_the_diagonal_should_count() {
    let (x, y) = (0,0);
    let coords = (x,y);
    let world = WorldMap::empty();
    let new_world = world.
        mark_cell_alive(coords).
        mark_cell_alive((x, y+1));
    let alive_neighbours_count = new_world.count_alive_neighbours(coords);
    assert_eq!(1, alive_neighbours_count);
}

#[test]
fn is_cell_alive_should_return_true_for_alive_cell() {
    let coords = (0,0);
    let world = WorldMap::empty();
    let new_world = world.mark_cell_alive(coords);
    let is_alive = new_world.is_cell_alive(coords);
    assert_eq!(true, is_alive);
}

#[test]
fn is_cell_alive_should_return_false_for_dead_cell() {
    let coords = (0,0);
    let world = WorldMap::empty();
    let is_alive = world.is_cell_alive(coords);
    assert_eq!(false, is_alive);
}
<commit_msg>all four rules for game of life implemented<commit_after>use std::collections::HashSet;

struct WorldMap{
    map: HashSet<(i8, i8)>
}

fn get_neighbours_coordinates(cell_coordinates: (i8,i8)) -> HashSet<(i8,i8)> {
    let mut neighbour_coordinates = HashSet::new();
    let (x, y) = cell_coordinates;
    neighbour_coordinates.insert((x-1, y-1));
    neighbour_coordinates.insert((x-1, y));
    neighbour_coordinates.insert((x-1, y+1));
    neighbour_coordinates.insert((x, y-1));
    neighbour_coordinates.insert((x, y+1));
    neighbour_coordinates.insert((x+1, y-1));
    neighbour_coordinates.insert((x+1, y));
    neighbour_coordinates.insert((x+1, y+1));
    return neighbour_coordinates;
}

impl WorldMap{

    fn empty() -> WorldMap {
        return WorldMap::new( HashSet::new() );
    }

    fn new(new_map: HashSet<(i8,i8)>) -> WorldMap {
        return WorldMap { map: new_map };
    }

    fn mark_cell_alive(&self, cell_coordinates: (i8, i8)) -> WorldMap {
        let mut new_map = self.map.clone();
        new_map.insert(cell_coordinates);
        let new_world = WorldMap::new(new_map);
        return new_world;
    }

    fn is_cell_alive(&self, cell_coordinates: (i8,i8)) -> bool {
        return self.map.contains(&cell_coordinates);
    }

    fn count_alive_neighbours(&self, cell_coordinates: (i8, i8)) -> usize {
        let neighbour_coordinates = get_neighbours_coordinates(cell_coordinates);
        let neighbours_alive = self.map.intersection(&neighbour_coordinates).collect::<Vec<&(i8,i8)>>();
        return neighbours_alive.len();
    }

    fn should_live(&self, cell_coordinates: (i8,i8)) -> bool {
        let is_alive = self.is_cell_alive(cell_coordinates);
        let alive_neighbours_count = self.count_alive_neighbours(cell_coordinates);

        // should live in the next iteration if alive now
        let result_for_alive = match alive_neighbours_count {
            0...1 => false,
            2...3 => true,
            4...8 => false,
            _ => panic!("wrong number of neighbours: {}", alive_neighbours_count)
        };

        // should be alive in the next iteration if dead now
        let result_for_dead = match alive_neighbours_count {
            3 => true,
            _ => false
        };

        let result = match is_alive {
            true => result_for_alive,
            false => result_for_dead
        };
        return result;
    }
}

#[test]
// rule 1
fn alive_cell_with_no_alive_neighbours_should_die() {
    let coords = (0,0);
    let world = WorldMap::empty();
    let new_world = world.mark_cell_alive(coords);
    let should_die = !new_world.should_live(coords);
    assert!(should_die);
}

#[test]
// rule 1
fn alive_cell_with_one_alive_neighbour_should_die() {
    let (x, y) = (0,0);
    let coords = (x,y);
    let world = WorldMap::empty();
    let new_world = world.
        mark_cell_alive(coords).
        mark_cell_alive((x, y+1));
    let should_die = !new_world.should_live(coords);
    assert!(should_die);
}

#[test]
// rule 2
fn alive_cell_with_two_alive_neighbours_should_stay_alive() {
    let (x, y) = (0,0);
    let coords = (x,y);
    let world = WorldMap::empty();
    let new_world = world.
        mark_cell_alive(coords).
        mark_cell_alive((x, y+1)).
        mark_cell_alive((x-1, y+1));
    let should_live = new_world.should_live(coords);
    assert!(should_live);
}

#[test]
// rule 2
fn alive_cell_with_three_alive_neighbours_should_stay_alive() {
    let (x, y) = (0,0);
    let coords = (x,y);
    let world = WorldMap::empty();
    let new_world = world.
        mark_cell_alive(coords).
        mark_cell_alive((x, y+1)).
        mark_cell_alive((x-1, y)).
        mark_cell_alive((x-1, y+1));
    let should_live = new_world.should_live(coords);
    assert!(should_live);
}

#[test]
// rule 3
fn alive_cell_with_four_alive_neighbours_should_die() {
    let (x, y) = (0,0);
    let coords = (x,y);
    let world = WorldMap::empty();
    let new_world = world.
        mark_cell_alive(coords).
        mark_cell_alive((x, y+1)).
        mark_cell_alive((x-1, y)).
        mark_cell_alive((x-1, y+1)).
        mark_cell_alive((x+1, y+1));
    let should_die = !new_world.should_live(coords);
    assert!(should_die);
}

#[test]
// rule 4
fn dead_cell_with_three_alive_neighbours_should_come_alive() {
    let (x, y) = (0,0);
    let coords = (x,y);
    let world = WorldMap::empty();
    let new_world = world.
        mark_cell_alive((x, y+1)).
        mark_cell_alive((x-1, y)).
        mark_cell_alive((x-1, y+1));
    let should_live = new_world.should_live(coords);
    assert!(should_live);
}

#[test]
fn dead_cell_with_dead_alive_neighbours_should_stay_dead() {
    let (x, y) = (0,0);
    let coords = (x,y);
    let world = WorldMap::empty();
    let new_world = world.
        mark_cell_alive((x, y+1)).
        mark_cell_alive((x-1, y));
    let should_die = !new_world.should_live(coords);
    assert!(should_die);
}
#[test]
fn the_only_alive_cell_should_have_no_alive_neighbours() {
    let coords = (0,0);
    let world = WorldMap::empty();
    let new_world = world.mark_cell_alive(coords);
    let alive_neighbours_count = new_world.count_alive_neighbours(coords);
    assert_eq!(0, alive_neighbours_count);
}

#[test]
fn alive_neighbour_on_the_diagonal_should_count() {
    let (x, y) = (0,0);
    let coords = (x,y);
    let world = WorldMap::empty();
    let new_world = world.
        mark_cell_alive(coords).
        mark_cell_alive((x, y+1));
    let alive_neighbours_count = new_world.count_alive_neighbours(coords);
    assert_eq!(1, alive_neighbours_count);
}

#[test]
fn is_cell_alive_should_return_true_for_alive_cell() {
    let coords = (0,0);
    let world = WorldMap::empty();
    let new_world = world.mark_cell_alive(coords);
    let is_alive = new_world.is_cell_alive(coords);
    assert_eq!(true, is_alive);
}

#[test]
fn is_cell_alive_should_return_false_for_dead_cell() {
    let coords = (0,0);
    let world = WorldMap::empty();
    let is_alive = world.is_cell_alive(coords);
    assert_eq!(false, is_alive);
}
<|endoftext|>


<commit_before>use eyre::Result;
use nom::{
    branch::alt,
    bytes::complete::{tag, take, take_until, take_while1},
    character::complete::{char, digit1, multispace0, multispace1},
    combinator::{map_res, not, peek},
    multi::{fold_many0, separated_list0},
    sequence::{delimited, preceded, tuple},
    IResult,
};
use stencila_schema::{
    Article, BlockContent, Cite, CiteGroup, CodeBlock, CodeFragment, Delete, Emphasis, Heading,
    InlineContent, Link, MathFragment, Node, Paragraph, Strong, Subscript, Superscript,
    ThematicBreak,
};

/// Decode a Markdown document to a `Node`
///
/// Intended for decoding an entire document, this function extracts
/// YAML front matter, parses the Markdown, and returns a `Node::Article` variant.
pub fn decode(md: &str) -> Result<Node> {
    let content = decode_fragment(md);

    let article = Article {
        content: Some(content),
        ..Default::default()
    };

    Ok(Node::Article(article))
}

/// Decode a Markdown fragment to a vector of `BlockContent`
///
/// Intended for decoding a fragment of Markdown (e.g. a Markdown cell in
/// a Jupyter Notebook) and inserting it into a larger document.
///
/// Uses the `pulldown_cmark` and transforms its `Event`s into
/// `Vec<BlockContent>`. Text is further parsed using `nom` based parsers
/// to handle the elements that `pulldown_cmark` does not handle (e.g. math).
pub fn decode_fragment(md: &str) -> Vec<BlockContent> {
    use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};

    // Holds any text content
    let mut texts = String::new();
    // Holds and manages inline content nodes
    struct Inlines {
        nodes: Vec<InlineContent>,
        marks: Vec<usize>,
    }

    impl Inlines {
        fn clear(&mut self) {
            self.nodes.clear();
            self.marks.clear()
        }

        fn mark(&mut self) {
            self.marks.push(self.nodes.len())
        }

        fn push(&mut self, node: InlineContent) {
            self.nodes.push(node)
        }

        fn append(&mut self, nodes: &mut Vec<InlineContent>) {
            self.nodes.append(nodes)
        }

        fn condense(mut nodes: Vec<InlineContent>) -> Vec<InlineContent> {
            let mut index = 1;
            while index < nodes.len() {
                // FIXME: Avoid this clone?
                let curr = nodes[index].clone();
                let prev = &mut nodes[index - 1];
                match (prev, curr) {
                    (InlineContent::String(prev), InlineContent::String(curr)) => {
                        if prev.ends_with(|chr: char| !chr.is_whitespace()) && curr == "\u{2029}" {
                            prev.push(' ');
                        } else {
                            prev.push_str(&curr);
                        }
                        nodes.remove(index);
                    }
                    _ => index += 1,
                }
            }
            nodes
        }

        fn take_tail(&mut self) -> Vec<InlineContent> {
            let n = self.marks.pop().expect("Unable to pop marks!");
            Inlines::condense(self.nodes.split_off(n))
        }

        fn take_all(&mut self) -> Vec<InlineContent> {
            Inlines::condense(self.nodes.split_off(0))
        }
    }

    let mut inlines = Inlines {
        nodes: Vec::new(),
        marks: Vec::new(),
    };

    // Holds the block content nodes
    let mut blocks: Vec<BlockContent> = Vec::new();

    let parser = Parser::new_ext(md, Options::all());
    for event in parser {
        match event {
            Event::Start(tag) => match tag {
                Tag::Heading(_) => inlines.clear(),
                Tag::Paragraph => inlines.clear(),
                Tag::CodeBlock(_) => (),
                Tag::Emphasis => inlines.mark(),
                Tag::Strong => inlines.mark(),
                Tag::Strikethrough => inlines.mark(),
                Tag::Link(_, _, _) => inlines.mark(),
                _ => println!("Start {:?}", tag),
            },
            Event::End(tag) => match tag {
                Tag::Heading(depth) => blocks.push(BlockContent::Heading(Heading {
                    depth: Some(Box::new(depth as i64)),
                    content: inlines.take_all(),
                    ..Default::default()
                })),
                Tag::Paragraph => blocks.push(BlockContent::Paragraph(Paragraph {
                    content: inlines.take_all(),
                    ..Default::default()
                })),
                Tag::CodeBlock(kind) => {
                    let text = texts.clone();
                    texts.clear();
                    blocks.push(BlockContent::CodeBlock(CodeBlock {
                        text,
                        programming_language: match kind {
                            CodeBlockKind::Fenced(lang) => {
                                let lang = lang.to_string();
                                if !lang.is_empty() {
                                    Some(Box::new(lang))
                                } else {
                                    None
                                }
                            }
                            _ => None,
                        },
                        ..Default::default()
                    }))
                }
                Tag::Emphasis => {
                    let content = inlines.take_tail();
                    inlines.push(InlineContent::Emphasis(Emphasis {
                        content,
                        ..Default::default()
                    }))
                }
                Tag::Strong => {
                    let content = inlines.take_tail();
                    inlines.push(InlineContent::Strong(Strong {
                        content,
                        ..Default::default()
                    }))
                }
                Tag::Strikethrough => {
                    let content = inlines.take_tail();
                    inlines.push(InlineContent::Delete(Delete {
                        content,
                        ..Default::default()
                    }))
                }
                Tag::Link(_link_type, url, title) => {
                    let content = inlines.take_tail();
                    inlines.push(InlineContent::Link(Link {
                        content,
                        target: url.to_string(),
                        title: Some(Box::new(title.to_string())),
                        ..Default::default()
                    }))
                }
                _ => (),
            },
            Event::Text(value) => {
                // Accumulate to inline content after parsing
                let mut content = decode_inline_content(&value);
                inlines.append(&mut content);

                // Accumulate to text (needed for indented (unfenced) code blocks)
                texts.push_str(&value.to_string())
            }
            Event::Code(value) => {
                inlines.push(InlineContent::CodeFragment(CodeFragment {
                    text: value.to_string(),
                    ..Default::default()
                }));
            }
            Event::Html(value) => {
                tracing::warn!("Unhandled Markdown element Html {}", value);
            }
            Event::FootnoteReference(value) => {
                tracing::warn!("Unhandled Markdown element FootnoteReference {}", value);
            }
            Event::SoftBreak => {
                // A soft line break event occurs between lines of a multi-line paragraph
                // (between a `Text` event for each line). This inserts the Unicode soft break
                // character so that, when inlines are condensed a space can be added if
                // necessary.
                inlines.push(InlineContent::String("\u{2029}".to_string()))
            }
            Event::HardBreak => {
                tracing::warn!("Unhandled Markdown element HardBreak");
            }
            Event::Rule => blocks.push(BlockContent::ThematicBreak(ThematicBreak {
                ..Default::default()
            })),
            Event::TaskListMarker(value) => {
                tracing::warn!("Unhandled Markdown element TaskListMarker {}", value);
            }
        };
    }

    blocks
}

/// Decode a string into a vector of `InlineContent` nodes
///
/// This is the entry point into `nom` Markdown parsing functions.
/// It is infallible in that if there is a parse error,
/// the original input string is returned as the only item
/// in the vector (with a warning).
fn decode_inline_content(input: &str) -> Vec<InlineContent> {
    match inline_content(input) {
        Ok((_, content)) => content,
        Err(error) => {
            tracing::warn!("While parsing inline content: {}", error);
            vec![InlineContent::String(String::from(input))]
        }
    }
}

/// Parse a string into a vector of `InlineContent` nodes
///
/// Whilst accumulating, will combine adjacent `String` nodes.
/// This is necessary because of the catch all `character` parser.
fn inline_content(input: &str) -> IResult<&str, Vec<InlineContent>> {
    fold_many0(
        alt((
            cite_group,
            cite,
            math,
            subscript,
            superscript,
            string,
            character,
        )),
        Vec::new(),
        |mut vec: Vec<InlineContent>, node| {
            if let InlineContent::String(string) = &node {
                match vec.last_mut() {
                    Some(InlineContent::String(last)) => last.push_str(&string),
                    _ => vec.push(node),
                }
            } else {
                vec.push(node)
            }
            vec
        },
    )(input)
}

/// Parse a string into a `CiteGroup` node
///
/// Simply collects several `Cite` notes into a `CiteGroup`.
pub fn cite_group(input: &str) -> IResult<&str, InlineContent> {
    map_res(
        delimited(
            char('['),
            separated_list0(delimited(multispace0, tag(";"), multispace0), cite),
            char(']'),
        ),
        |items: Vec<InlineContent>| -> Result<InlineContent> {
            Ok(InlineContent::CiteGroup(CiteGroup {
                items: items
                    .iter()
                    .filter_map(|item| match item {
                        InlineContent::Cite(cite) => Some(cite),
                        _ => None,
                    })
                    .cloned()
                    .collect(),
                ..Default::default()
            }))
        },
    )(input)
}

/// Parse a string into a `Cite` node
///
/// This attempts to follow Pandoc's citation handling as closely as possible
/// (see https://pandoc.org/MANUAL.html#citations).
///
/// The following properties of a `Cite` are parsed:
///   - [x] target
///   - [ ] citation_mode
///   - [ ] page_start
///   - [ ] page_end
///   - [ ] pagination
///   - [ ] citation_prefix
///   - [ ] citation_suffix
///   - [ ] citation_intent
pub fn cite(input: &str) -> IResult<&str, InlineContent> {
    map_res(
        preceded(char('@'), take_while1(|chr: char| chr.is_alphanumeric())),
        |res: &str| -> Result<InlineContent> {
            let target = res.into();
            Ok(InlineContent::Cite(Cite {
                target,
                ..Default::default()
            }))
        },
    )(input)
}

/// Parse a string into an `InlineContent` node
///
/// This attempts to follow Pandoc's match parsing as closely as possible
/// (see https://pandoc.org/MANUAL.html#math).
pub fn math(input: &str) -> IResult<&str, InlineContent> {
    map_res(
        delimited(
            // Pandoc: "opening $ must have a non-space character immediately to its right"
            tuple((char('$'), peek(not(multispace1)))),
            take_until("$"),
            // Pandoc: "the closing $ must have a non-space character immediately to its left,
            // and must not be followed immediately by a digit"
            tuple((peek(not(multispace1)), char('$'), peek(not(digit1)))),
        ),
        |res: &str| -> Result<InlineContent> {
            Ok(InlineContent::MathFragment(MathFragment {
                text: res.into(),
                ..Default::default()
            }))
        },
    )(input)
}

/// Parse a string into a `Subscript` node
pub fn subscript(input: &str) -> IResult<&str, InlineContent> {
    map_res(
        delimited(char('~'), take_until("~"), char('~')),
        |res: &str| -> Result<InlineContent> {
            Ok(InlineContent::Subscript(Subscript {
                content: vec![InlineContent::String(res.into())],
                ..Default::default()
            }))
        },
    )(input)
}

/// Parse a string into a `Superscript` node
pub fn superscript(input: &str) -> IResult<&str, InlineContent> {
    map_res(
        delimited(char('^'), take_until("^"), char('^')),
        |res: &str| -> Result<InlineContent> {
            Ok(InlineContent::Superscript(Superscript {
                content: vec![InlineContent::String(res.into())],
                ..Default::default()
            }))
        },
    )(input)
}

/// Accumulate characters into a `String` node
///
/// Will greedily take as many characters as possible, excluding those that appear at the
/// start of other inline parsers e.g. '$', '['
fn string(input: &str) -> IResult<&str, InlineContent> {
    map_res(
        take_while1(|chr: char| chr != '^' && chr != '~' && chr != '$' && chr != '['),
        |res: &str| -> Result<InlineContent> { Ok(InlineContent::String(String::from(res))) },
    )(input)
}

/// Take a single character into a `String` node
///
/// Necessary so that the characters no consumed by `string` are not lost.
fn character(input: &str) -> IResult<&str, InlineContent> {
    map_res(take(1usize), |res: &str| -> Result<InlineContent> {
        Ok(InlineContent::String(String::from(res)))
    })(input)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::utils::tests::snapshot_content;
    use insta::assert_json_snapshot;

    #[test]
    fn fragments() {
        snapshot_content("fragments/md/*.md", |content| {
            assert_json_snapshot!(decode_fragment(&content));
        });
    }
}
<commit_msg>fix(Markdown links): Handle no title correctly<commit_after>use eyre::Result;
use nom::{
    branch::alt,
    bytes::complete::{tag, take, take_until, take_while1},
    character::complete::{char, digit1, multispace0, multispace1},
    combinator::{map_res, not, peek},
    multi::{fold_many0, separated_list0},
    sequence::{delimited, preceded, tuple},
    IResult,
};
use stencila_schema::{
    Article, BlockContent, Cite, CiteGroup, CodeBlock, CodeFragment, Delete, Emphasis, Heading,
    InlineContent, Link, MathFragment, Node, Paragraph, Strong, Subscript, Superscript,
    ThematicBreak,
};

/// Decode a Markdown document to a `Node`
///
/// Intended for decoding an entire document, this function extracts
/// YAML front matter, parses the Markdown, and returns a `Node::Article` variant.
pub fn decode(md: &str) -> Result<Node> {
    let content = decode_fragment(md);

    let article = Article {
        content: Some(content),
        ..Default::default()
    };

    Ok(Node::Article(article))
}

/// Decode a Markdown fragment to a vector of `BlockContent`
///
/// Intended for decoding a fragment of Markdown (e.g. a Markdown cell in
/// a Jupyter Notebook) and inserting it into a larger document.
///
/// Uses the `pulldown_cmark` and transforms its `Event`s into
/// `Vec<BlockContent>`. Text is further parsed using `nom` based parsers
/// to handle the elements that `pulldown_cmark` does not handle (e.g. math).
pub fn decode_fragment(md: &str) -> Vec<BlockContent> {
    use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};

    // Holds any text content
    let mut texts = String::new();
    // Holds and manages inline content nodes
    struct Inlines {
        nodes: Vec<InlineContent>,
        marks: Vec<usize>,
    }

    impl Inlines {
        fn clear(&mut self) {
            self.nodes.clear();
            self.marks.clear()
        }

        fn mark(&mut self) {
            self.marks.push(self.nodes.len())
        }

        fn push(&mut self, node: InlineContent) {
            self.nodes.push(node)
        }

        fn append(&mut self, nodes: &mut Vec<InlineContent>) {
            self.nodes.append(nodes)
        }

        fn condense(mut nodes: Vec<InlineContent>) -> Vec<InlineContent> {
            let mut index = 1;
            while index < nodes.len() {
                // FIXME: Avoid this clone?
                let curr = nodes[index].clone();
                let prev = &mut nodes[index - 1];
                match (prev, curr) {
                    (InlineContent::String(prev), InlineContent::String(curr)) => {
                        if prev.ends_with(|chr: char| !chr.is_whitespace()) && curr == "\u{2029}" {
                            prev.push(' ');
                        } else {
                            prev.push_str(&curr);
                        }
                        nodes.remove(index);
                    }
                    _ => index += 1,
                }
            }
            nodes
        }

        fn take_tail(&mut self) -> Vec<InlineContent> {
            let n = self.marks.pop().expect("Unable to pop marks!");
            Inlines::condense(self.nodes.split_off(n))
        }

        fn take_all(&mut self) -> Vec<InlineContent> {
            Inlines::condense(self.nodes.split_off(0))
        }
    }

    let mut inlines = Inlines {
        nodes: Vec::new(),
        marks: Vec::new(),
    };

    // Holds the block content nodes
    let mut blocks: Vec<BlockContent> = Vec::new();

    let parser = Parser::new_ext(md, Options::all());
    for event in parser {
        match event {
            Event::Start(tag) => match tag {
                Tag::Heading(_) => inlines.clear(),
                Tag::Paragraph => inlines.clear(),
                Tag::CodeBlock(_) => (),
                Tag::Emphasis => inlines.mark(),
                Tag::Strong => inlines.mark(),
                Tag::Strikethrough => inlines.mark(),
                Tag::Link(_, _, _) => inlines.mark(),
                _ => (),
            },
            Event::End(tag) => match tag {
                Tag::Heading(depth) => blocks.push(BlockContent::Heading(Heading {
                    depth: Some(Box::new(depth as i64)),
                    content: inlines.take_all(),
                    ..Default::default()
                })),
                Tag::Paragraph => blocks.push(BlockContent::Paragraph(Paragraph {
                    content: inlines.take_all(),
                    ..Default::default()
                })),
                Tag::CodeBlock(kind) => {
                    let text = texts.clone();
                    texts.clear();
                    blocks.push(BlockContent::CodeBlock(CodeBlock {
                        text,
                        programming_language: match kind {
                            CodeBlockKind::Fenced(lang) => {
                                let lang = lang.to_string();
                                if !lang.is_empty() {
                                    Some(Box::new(lang))
                                } else {
                                    None
                                }
                            }
                            _ => None,
                        },
                        ..Default::default()
                    }))
                }
                Tag::Emphasis => {
                    let content = inlines.take_tail();
                    inlines.push(InlineContent::Emphasis(Emphasis {
                        content,
                        ..Default::default()
                    }))
                }
                Tag::Strong => {
                    let content = inlines.take_tail();
                    inlines.push(InlineContent::Strong(Strong {
                        content,
                        ..Default::default()
                    }))
                }
                Tag::Strikethrough => {
                    let content = inlines.take_tail();
                    inlines.push(InlineContent::Delete(Delete {
                        content,
                        ..Default::default()
                    }))
                }
                Tag::Link(_link_type, url, title) => {
                    let content = inlines.take_tail();
                    let title = {
                        let title = title.to_string();
                        if !title.is_empty() {
                            Some(Box::new(title))
                        } else {
                            None
                        }
                    };
                    inlines.push(InlineContent::Link(Link {
                        content,
                        target: url.to_string(),
                        title,
                        ..Default::default()
                    }))
                }
                _ => {
                    tracing::warn!("Unhandled Markdown tag {:?}", tag);
                }
            },
            Event::Text(value) => {
                // Accumulate to inline content after parsing
                let mut content = decode_inline_content(&value);
                inlines.append(&mut content);

                // Accumulate to text (needed for indented (unfenced) code blocks)
                texts.push_str(&value.to_string())
            }
            Event::Code(value) => {
                inlines.push(InlineContent::CodeFragment(CodeFragment {
                    text: value.to_string(),
                    ..Default::default()
                }));
            }
            Event::Html(value) => {
                tracing::warn!("Unhandled Markdown element Html {}", value);
            }
            Event::FootnoteReference(value) => {
                tracing::warn!("Unhandled Markdown element FootnoteReference {}", value);
            }
            Event::SoftBreak => {
                // A soft line break event occurs between lines of a multi-line paragraph
                // (between a `Text` event for each line). This inserts the Unicode soft break
                // character so that, when inlines are condensed a space can be added if
                // necessary.
                inlines.push(InlineContent::String("\u{2029}".to_string()))
            }
            Event::HardBreak => {
                tracing::warn!("Unhandled Markdown element HardBreak");
            }
            Event::Rule => blocks.push(BlockContent::ThematicBreak(ThematicBreak {
                ..Default::default()
            })),
            Event::TaskListMarker(value) => {
                tracing::warn!("Unhandled Markdown element TaskListMarker {}", value);
            }
        };
    }

    blocks
}

/// Decode a string into a vector of `InlineContent` nodes
///
/// This is the entry point into `nom` Markdown parsing functions.
/// It is infallible in that if there is a parse error,
/// the original input string is returned as the only item
/// in the vector (with a warning).
fn decode_inline_content(input: &str) -> Vec<InlineContent> {
    match inline_content(input) {
        Ok((_, content)) => content,
        Err(error) => {
            tracing::warn!("While parsing inline content: {}", error);
            vec![InlineContent::String(String::from(input))]
        }
    }
}

/// Parse a string into a vector of `InlineContent` nodes
///
/// Whilst accumulating, will combine adjacent `String` nodes.
/// This is necessary because of the catch all `character` parser.
fn inline_content(input: &str) -> IResult<&str, Vec<InlineContent>> {
    fold_many0(
        alt((
            cite_group,
            cite,
            math,
            subscript,
            superscript,
            string,
            character,
        )),
        Vec::new(),
        |mut vec: Vec<InlineContent>, node| {
            if let InlineContent::String(string) = &node {
                match vec.last_mut() {
                    Some(InlineContent::String(last)) => last.push_str(&string),
                    _ => vec.push(node),
                }
            } else {
                vec.push(node)
            }
            vec
        },
    )(input)
}

/// Parse a string into a `CiteGroup` node
///
/// Simply collects several `Cite` notes into a `CiteGroup`.
pub fn cite_group(input: &str) -> IResult<&str, InlineContent> {
    map_res(
        delimited(
            char('['),
            separated_list0(delimited(multispace0, tag(";"), multispace0), cite),
            char(']'),
        ),
        |items: Vec<InlineContent>| -> Result<InlineContent> {
            Ok(InlineContent::CiteGroup(CiteGroup {
                items: items
                    .iter()
                    .filter_map(|item| match item {
                        InlineContent::Cite(cite) => Some(cite),
                        _ => None,
                    })
                    .cloned()
                    .collect(),
                ..Default::default()
            }))
        },
    )(input)
}

/// Parse a string into a `Cite` node
///
/// This attempts to follow Pandoc's citation handling as closely as possible
/// (see https://pandoc.org/MANUAL.html#citations).
///
/// The following properties of a `Cite` are parsed:
///   - [x] target
///   - [ ] citation_mode
///   - [ ] page_start
///   - [ ] page_end
///   - [ ] pagination
///   - [ ] citation_prefix
///   - [ ] citation_suffix
///   - [ ] citation_intent
pub fn cite(input: &str) -> IResult<&str, InlineContent> {
    map_res(
        preceded(char('@'), take_while1(|chr: char| chr.is_alphanumeric())),
        |res: &str| -> Result<InlineContent> {
            let target = res.into();
            Ok(InlineContent::Cite(Cite {
                target,
                ..Default::default()
            }))
        },
    )(input)
}

/// Parse a string into an `InlineContent` node
///
/// This attempts to follow Pandoc's match parsing as closely as possible
/// (see https://pandoc.org/MANUAL.html#math).
pub fn math(input: &str) -> IResult<&str, InlineContent> {
    map_res(
        delimited(
            // Pandoc: "opening $ must have a non-space character immediately to its right"
            tuple((char('$'), peek(not(multispace1)))),
            take_until("$"),
            // Pandoc: "the closing $ must have a non-space character immediately to its left,
            // and must not be followed immediately by a digit"
            tuple((peek(not(multispace1)), char('$'), peek(not(digit1)))),
        ),
        |res: &str| -> Result<InlineContent> {
            Ok(InlineContent::MathFragment(MathFragment {
                text: res.into(),
                ..Default::default()
            }))
        },
    )(input)
}

/// Parse a string into a `Subscript` node
pub fn subscript(input: &str) -> IResult<&str, InlineContent> {
    map_res(
        delimited(char('~'), take_until("~"), char('~')),
        |res: &str| -> Result<InlineContent> {
            Ok(InlineContent::Subscript(Subscript {
                content: vec![InlineContent::String(res.into())],
                ..Default::default()
            }))
        },
    )(input)
}

/// Parse a string into a `Superscript` node
pub fn superscript(input: &str) -> IResult<&str, InlineContent> {
    map_res(
        delimited(char('^'), take_until("^"), char('^')),
        |res: &str| -> Result<InlineContent> {
            Ok(InlineContent::Superscript(Superscript {
                content: vec![InlineContent::String(res.into())],
                ..Default::default()
            }))
        },
    )(input)
}

/// Accumulate characters into a `String` node
///
/// Will greedily take as many characters as possible, excluding those that appear at the
/// start of other inline parsers e.g. '$', '['
fn string(input: &str) -> IResult<&str, InlineContent> {
    map_res(
        take_while1(|chr: char| chr != '^' && chr != '~' && chr != '$' && chr != '['),
        |res: &str| -> Result<InlineContent> { Ok(InlineContent::String(String::from(res))) },
    )(input)
}

/// Take a single character into a `String` node
///
/// Necessary so that the characters no consumed by `string` are not lost.
fn character(input: &str) -> IResult<&str, InlineContent> {
    map_res(take(1usize), |res: &str| -> Result<InlineContent> {
        Ok(InlineContent::String(String::from(res)))
    })(input)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::utils::tests::snapshot_content;
    use insta::assert_json_snapshot;

    #[test]
    fn fragments() {
        snapshot_content("fragments/md/*.md", |content| {
            assert_json_snapshot!(decode_fragment(&content));
        });
    }
}
<|endoftext|>


<commit_before>//============================================================================
//
// A simple Mandelbrot image generator in Rust
//
// Copyright (c) 2014 Gavin Baker <gavinb@antonym.org>
// Published under the MIT license
//
// Packages required:
//
// - OpenGLES
// - GLFW
// - cgmath
//
//============================================================================

#[license = "BSD"];

#[allow(deprecated_owned_vector)];
#[allow(dead_code)];

extern crate native;
extern crate num;

extern crate opengles;
extern crate glfw;
extern crate cgmath;

use opengles::gl2;

use cgmath::vector::Vec3;
use cgmath::aabb::Aabb3;

use num::complex::Complex64;
use std::num::{sin};

type Vec3f = Vec3<f32>;
type AABB3f = Aabb3<f32>;

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

static vertex_shader_source: &'static str = "

#version 150

in vec2 position;
in vec3 color;

out vec3 Color;

void main()
{
    Color = color;
    gl_Position = vec4(position, 0.0, 1.0);
}
";

static fragment_shader_source: &'static str = "

#version 150

in vec3 Color;
out vec4 outColor;

void main()
{
    outColor = vec4(Color, 1.0);
}
";

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

struct ErrorContext;
impl glfw::ErrorCallback for ErrorContext {
    fn call(&self, _: glfw::Error, description: ~str) {
        println!("GLFW Error: {:s}", description);
    }
}

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

struct WindowController<'a> {
    window: &'a glfw::Window,
    vertices: ~[f32],
    vao: ~[gl2::GLuint],
    vbo: ~[gl2::GLuint],
    vertex_shader: gl2::GLuint,
    fragment_shader: gl2::GLuint,
    shader_program: gl2::GLuint,
    uni_color: gl2::GLint,
}

impl<'a> WindowController<'a> {
    fn new(window: &'a glfw::Window) -> WindowController<'a> {
        let mut wc = WindowController { window: window,
                                        vao: ~[0],
                                        vbo: ~[0],
                                        vertices: ~[],
                                        vertex_shader: 0,
                                        fragment_shader: 0,
                                        shader_program: 0,
                                        uni_color: 0,
        };

        let (width, height) =  wc.window.get_framebuffer_size();

        gl2::viewport(0, 0, width as i32, height as i32);

        println!("Viewport: {} x {}", width, height);

        wc.vertices = ~[
            0.0,  0.5, 1.0, 0.0, 0.0,
            0.5, -0.5, 0.0, 1.0, 0.0,
            -0.5, -0.5, 0.0, 0.0, 1.0,
            ];

        // VAO

        wc.vao = gl2::gen_vertex_arrays(1);
        gl2::bind_vertex_array(wc.vao[0]);

        // VBO

        wc.vbo = gl2::gen_buffers(1);
        gl2::bind_buffer(gl2::ARRAY_BUFFER, wc.vbo[0]);
        gl2::buffer_data(gl2::ARRAY_BUFFER, wc.vertices, gl2::STATIC_DRAW);

        // Vertex Shader

        wc.vertex_shader = gl2::create_shader(gl2::VERTEX_SHADER);

        if wc.vertex_shader == 0 {
            fail!("Create v.shader failed");
        }

        gl2::shader_source(wc.vertex_shader, [vertex_shader_source.to_owned().as_bytes()]);

        let err = gl2::get_error();
        if err != 0 {
            fail!("glShaderSource.v err 0x{:x}", err);
        }

        gl2::compile_shader(wc.vertex_shader);

        let err = gl2::get_error();
        if err != 0 {
            fail!("glCompileShader.f err 0x{:x}", err);
        }

        let status = gl2::get_shader_iv(wc.vertex_shader, gl2::COMPILE_STATUS);

        if status != gl2::TRUE as i32 {
            let log = gl2::get_shader_info_log(wc.vertex_shader);
            fail!("glCompileShader.v err 0x{:x}: {}", status, log);
        }

        // Fragment Shader

        wc.fragment_shader = gl2::create_shader(gl2::FRAGMENT_SHADER);

        if wc.vertex_shader == 0 {
            fail!("Create f.shader failed");
        }

        gl2::shader_source(wc.fragment_shader, [fragment_shader_source.to_owned().as_bytes()]);

        let err = gl2::get_error();
        if err != 0 {
            fail!("glShaderSource.f -> 0x{:x}", err);
        }

        gl2::compile_shader(wc.fragment_shader);

        let err = gl2::get_error();
        if err != 0 {
            fail!("glCompileShader.v err 0x{:x}", err);
        }

        let status = gl2::get_shader_iv(wc.fragment_shader, gl2::COMPILE_STATUS);

        if status != gl2::TRUE as i32 {
            let log = gl2::get_shader_info_log(wc.fragment_shader);
            fail!("glCompileShader.f err 0x{:x}: {}", status, log);
        }

        // Link

        wc.shader_program = gl2::create_program();

        if wc.shader_program == 0 {
            fail!("glCreateProgram failed");
        }

        gl2::attach_shader(wc.shader_program, wc.vertex_shader);
        gl2::attach_shader(wc.shader_program, wc.fragment_shader);

        let err = gl2::get_error();
        if err != 0 {
            fail!("glAttachShader err 0x{:x}", err);
        }

        gl2::link_program(wc.shader_program);

        let err = gl2::get_error();
        if err != 0 {
            fail!("glLinkProgram err 0x{:x}", err);
        }

        let status = gl2::get_program_iv(wc.shader_program, gl2::LINK_STATUS);

        if status != gl2::TRUE as i32 {
            let log = gl2::get_shader_info_log(wc.shader_program);
            fail!("glLinkProgram err {}: {}", status, log);
        }

        gl2::use_program(wc.shader_program);

        let err = gl2::get_error();
        if err != 0 {
            let log = gl2::get_program_info_log(wc.shader_program);
            fail!("glUseProgram error: {}", log);
        }

        // Attributes

        let pos_attrib = gl2::get_attrib_location(wc.shader_program, "position");
        gl2::enable_vertex_attrib_array(pos_attrib as gl2::GLuint);
        gl2::vertex_attrib_pointer_f32(pos_attrib as gl2::GLuint, 2, false, 5*4, 0);

        let col_attrib = gl2::get_attrib_location(wc.shader_program, "color");
        gl2::enable_vertex_attrib_array(col_attrib as gl2::GLuint);
        gl2::vertex_attrib_pointer_f32(col_attrib as gl2::GLuint, 3, false, 5*4, 2*4);

        // Uniforms

        wc.uni_color = gl2::get_uniform_location(wc.shader_program, "triangleColor");

        let err = gl2::get_error();
        if err != 0 {
            fail!("attrib err = {:x}", err);
        }

        wc
    }

    fn draw(&self) {

        // Clear

        gl2::clear_color(0.4, 0.4, 0.4, 1.0);
        gl2::clear(gl2::COLOR_BUFFER_BIT);

        // Animate

        let time = glfw::get_time();
//        gl2::uniform_3f(wc.uni_color, (sin(time * 4.0) + 1.0) / 2.0, 0.0, 0.0);

        // Draw!

        gl2::draw_arrays(gl2::TRIANGLES, 0, 3);

        let err = gl2::get_error();
        if err != 0 {
            println!("draw err = 0x{:x}", err);
        }

        self.window.swap_buffers();
    }

    fn uninit(&mut self) {

        // Cleanup

        gl2::delete_program(self.shader_program);
        gl2::delete_shader(self.fragment_shader);
        gl2::delete_shader(self.vertex_shader);

        gl2::delete_buffers(self.vbo);

        // @todo Missing from bindings
        //gl2::delete_vertex_arrays(vao);
    }

    fn handle_window_event(&self, window: &glfw::Window, (time, event): (f64, glfw::WindowEvent)) {
        match event {

            glfw::CloseEvent => println!("Time: {}, Window close requested.", time),

            glfw::KeyEvent(key, scancode, action, mods) => {
                println!("Time: {}, Key: {}, ScanCode: {}, Action: {}, Modifiers: [{}]", time, key, scancode, action, mods);
                match (key, action) {
                    (glfw::KeyEscape, glfw::Press) => window.set_should_close(true),
                    (glfw::KeyR, glfw::Press) => {
                        // Resize should cause the window to "refresh"
                        let (window_width, window_height) = window.get_size();
                        window.set_size(window_width + 1, window_height);
                        window.set_size(window_width, window_height);
                    }
                    _ => {}
                }
            }
            _ => {}
        }
    }
}

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

struct MandelEngine {
    re0: f32,
    re1: f32,
    im0: f32,
    im1: f32,
    delta: f32,
}

impl MandelEngine {

    fn new() -> MandelEngine {
        MandelEngine { re0: -2.0, re1: 2.0, im0: -2.0, im1: 2.0, delta: 1.0 }
    }

    // Evalute entire region
    fn process() {
    }

    // Evaluate a single point
    fn mandel(z: Complex64) -> int {
        let maxiter: int = 80;
        let mut c: Complex64 = z;
        for n in range(0, maxiter) {
            if c.norm() > 2.0 {
                return n;
            }
            c = c*c+z;
        }
        return maxiter;
    }
}

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

#[start]
fn start(argc: int, argv: **u8) -> int {
    native::start(argc, argv, main)
}

fn main() {

    println!("GLFW version: {:s}", glfw::get_version_string());

    glfw::start(proc() {

        glfw::window_hint::context_version_major(3);
        glfw::window_hint::context_version_minor(2);
        glfw::window_hint::opengl_forward_compat(true);
        glfw::window_hint::opengl_profile(glfw::OpenGlCoreProfile);
        glfw::window_hint::resizable(false);

        let window = glfw::Window::create(640, 480,
                                          "MandelRust",
                                          glfw::Windowed)
            .expect("Failed to create GLFW window.");

        window.make_context_current();

        let win_ctrl = ~WindowController::new(&window);

        while !window.should_close() {
            glfw::poll_events();
            for event in window.flush_events() {
                win_ctrl.handle_window_event(&window, event);
            }
        }
    });
}
<commit_msg>Add proper looping of range to process, run in background<commit_after>//============================================================================
//
// A simple Mandelbrot image generator in Rust
//
// Copyright (c) 2014 Gavin Baker <gavinb@antonym.org>
// Published under the MIT license
//
// Packages required:
//
// - OpenGLES
// - GLFW
// - cgmath
//
//============================================================================

#[license = "BSD"];

#[allow(deprecated_owned_vector)];
#[allow(dead_code)];

extern crate native;
extern crate num;

extern crate opengles;
extern crate glfw;
extern crate cgmath;

use opengles::gl2;

use cgmath::vector::Vec3;
use cgmath::aabb::Aabb3;

use num::complex::{Cmplx, Complex64};

use std::num::{sin};

type Vec3f = Vec3<f32>;
type AABB3f = Aabb3<f32>;

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

static vertex_shader_source: &'static str = "

#version 150

in vec2 position;
in vec3 color;

out vec3 Color;

void main()
{
    Color = color;
    gl_Position = vec4(position, 0.0, 1.0);
}
";

static fragment_shader_source: &'static str = "

#version 150

in vec3 Color;
out vec4 outColor;

void main()
{
    outColor = vec4(Color, 1.0);
}
";

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

struct ErrorContext;
impl glfw::ErrorCallback for ErrorContext {
    fn call(&self, _: glfw::Error, description: ~str) {
        println!("GLFW Error: {:s}", description);
    }
}

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

struct WindowController<'a> {
    window: &'a glfw::Window,
    vertices: ~[f32],
    vao: ~[gl2::GLuint],
    vbo: ~[gl2::GLuint],
    vertex_shader: gl2::GLuint,
    fragment_shader: gl2::GLuint,
    shader_program: gl2::GLuint,
    uni_color: gl2::GLint,
}

impl<'a> WindowController<'a> {
    fn new(window: &'a glfw::Window) -> WindowController<'a> {
        let mut wc = WindowController { window: window,
                                        vao: ~[0],
                                        vbo: ~[0],
                                        vertices: ~[],
                                        vertex_shader: 0,
                                        fragment_shader: 0,
                                        shader_program: 0,
                                        uni_color: 0,
        };

        let (width, height) =  wc.window.get_framebuffer_size();

        gl2::viewport(0, 0, width as i32, height as i32);

        println!("Viewport: {} x {}", width, height);

        wc.vertices = ~[
            0.0,  0.5, 1.0, 0.0, 0.0,
            0.5, -0.5, 0.0, 1.0, 0.0,
            -0.5, -0.5, 0.0, 0.0, 1.0,
            ];

        // VAO

        wc.vao = gl2::gen_vertex_arrays(1);
        gl2::bind_vertex_array(wc.vao[0]);

        // VBO

        wc.vbo = gl2::gen_buffers(1);
        gl2::bind_buffer(gl2::ARRAY_BUFFER, wc.vbo[0]);
        gl2::buffer_data(gl2::ARRAY_BUFFER, wc.vertices, gl2::STATIC_DRAW);

        // Vertex Shader

        wc.vertex_shader = gl2::create_shader(gl2::VERTEX_SHADER);

        if wc.vertex_shader == 0 {
            fail!("Create v.shader failed");
        }

        gl2::shader_source(wc.vertex_shader, [vertex_shader_source.to_owned().as_bytes()]);

        let err = gl2::get_error();
        if err != 0 {
            fail!("glShaderSource.v err 0x{:x}", err);
        }

        gl2::compile_shader(wc.vertex_shader);

        let err = gl2::get_error();
        if err != 0 {
            fail!("glCompileShader.f err 0x{:x}", err);
        }

        let status = gl2::get_shader_iv(wc.vertex_shader, gl2::COMPILE_STATUS);

        if status != gl2::TRUE as i32 {
            let log = gl2::get_shader_info_log(wc.vertex_shader);
            fail!("glCompileShader.v err 0x{:x}: {}", status, log);
        }

        // Fragment Shader

        wc.fragment_shader = gl2::create_shader(gl2::FRAGMENT_SHADER);

        if wc.vertex_shader == 0 {
            fail!("Create f.shader failed");
        }

        gl2::shader_source(wc.fragment_shader, [fragment_shader_source.to_owned().as_bytes()]);

        let err = gl2::get_error();
        if err != 0 {
            fail!("glShaderSource.f -> 0x{:x}", err);
        }

        gl2::compile_shader(wc.fragment_shader);

        let err = gl2::get_error();
        if err != 0 {
            fail!("glCompileShader.v err 0x{:x}", err);
        }

        let status = gl2::get_shader_iv(wc.fragment_shader, gl2::COMPILE_STATUS);

        if status != gl2::TRUE as i32 {
            let log = gl2::get_shader_info_log(wc.fragment_shader);
            fail!("glCompileShader.f err 0x{:x}: {}", status, log);
        }

        // Link

        wc.shader_program = gl2::create_program();

        if wc.shader_program == 0 {
            fail!("glCreateProgram failed");
        }

        gl2::attach_shader(wc.shader_program, wc.vertex_shader);
        gl2::attach_shader(wc.shader_program, wc.fragment_shader);

        let err = gl2::get_error();
        if err != 0 {
            fail!("glAttachShader err 0x{:x}", err);
        }

        gl2::link_program(wc.shader_program);

        let err = gl2::get_error();
        if err != 0 {
            fail!("glLinkProgram err 0x{:x}", err);
        }

        let status = gl2::get_program_iv(wc.shader_program, gl2::LINK_STATUS);

        if status != gl2::TRUE as i32 {
            let log = gl2::get_shader_info_log(wc.shader_program);
            fail!("glLinkProgram err {}: {}", status, log);
        }

        gl2::use_program(wc.shader_program);

        let err = gl2::get_error();
        if err != 0 {
            let log = gl2::get_program_info_log(wc.shader_program);
            fail!("glUseProgram error: {}", log);
        }

        // Attributes

        let pos_attrib = gl2::get_attrib_location(wc.shader_program, "position");
        gl2::enable_vertex_attrib_array(pos_attrib as gl2::GLuint);
        gl2::vertex_attrib_pointer_f32(pos_attrib as gl2::GLuint, 2, false, 5*4, 0);

        let col_attrib = gl2::get_attrib_location(wc.shader_program, "color");
        gl2::enable_vertex_attrib_array(col_attrib as gl2::GLuint);
        gl2::vertex_attrib_pointer_f32(col_attrib as gl2::GLuint, 3, false, 5*4, 2*4);

        // Uniforms

        wc.uni_color = gl2::get_uniform_location(wc.shader_program, "triangleColor");

        let err = gl2::get_error();
        if err != 0 {
            fail!("attrib err = {:x}", err);
        }

        wc
    }

    fn draw(&self) {

        // Clear

        gl2::clear_color(0.4, 0.4, 0.4, 1.0);
        gl2::clear(gl2::COLOR_BUFFER_BIT);

        // Animate

        let time = glfw::get_time();
        gl2::uniform_3f(self.uni_color, (sin(time * 4.0f32 as f64) as f32 + 1.0f32) / 2.0f32, 0.0f32, 0.0f32);

        // Draw!

        gl2::draw_arrays(gl2::TRIANGLES, 0, 3);

        let err = gl2::get_error();
        if err != 0 {
            println!("draw err = 0x{:x}", err);
        }

        self.window.swap_buffers();
    }

    fn uninit(&mut self) {

        // Cleanup

        gl2::delete_program(self.shader_program);
        gl2::delete_shader(self.fragment_shader);
        gl2::delete_shader(self.vertex_shader);

        gl2::delete_buffers(self.vbo);

        // @todo Missing from bindings
        //gl2::delete_vertex_arrays(vao);
    }

    fn handle_window_event(&self, window: &glfw::Window, (time, event): (f64, glfw::WindowEvent)) {
        match event {

            glfw::CloseEvent => println!("Time: {}, Window close requested.", time),

            glfw::KeyEvent(key, scancode, action, mods) => {
                println!("Time: {}, Key: {}, ScanCode: {}, Action: {}, Modifiers: [{}]", time, key, scancode, action, mods);
                match (key, action) {
                    (glfw::KeyEscape, glfw::Press) => window.set_should_close(true),
                    (glfw::KeyR, glfw::Press) => {
                        // Resize should cause the window to "refresh"
                        let (window_width, window_height) = window.get_size();
                        window.set_size(window_width + 1, window_height);
                        window.set_size(window_width, window_height);
                    }
                    _ => {}
                }
            }
            _ => {}
        }
    }
}

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

struct MandelEngine {
    re0: f32,
    re1: f32,
    im0: f32,
    im1: f32,
    delta: f32,
}

impl MandelEngine {

    fn new() -> MandelEngine {
        MandelEngine { re0: -2.0, re1: 2.0, im0: -2.0, im1: 2.0, delta: 0.1 }
    }

    // Evalute entire region
    fn process(&self) {
        println!("+++ process: re: {}..{} im: {}..{}", self.re0, self.re1, self.im0, self.im1);
        // @todo range_step doesn't work for floats :( It needs CheckedAdd for f32. So we do loops manually.
        let mut re = self.re0;
        while re < self.re1 {
            let mut im = self.im0;
            while im < self.im1 {
                let z = Cmplx::new(re as f64, im as f64);
                println!("z={}+{}i, c={}", re, im, self.mandel(z));
                im += self.delta;
            }
            re += self.delta;
        }
        println!("--- process");
    }

    // Evaluate a single point
    fn mandel(&self, z: Complex64) -> uint {
        let maxiter: uint = 80;
        let mut c: Complex64 = z;
        for n in range(0, maxiter) {
            if c.norm() > 2.0 {
                return n;
            }
            c = c*c+z;
        }
        return maxiter;
    }
}

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

#[start]
fn start(argc: int, argv: **u8) -> int {
    native::start(argc, argv, main)
}

fn main() {

    println!("GLFW version: {:s}", glfw::get_version_string());

    glfw::start(proc() {

        glfw::window_hint::context_version_major(3);
        glfw::window_hint::context_version_minor(2);
        glfw::window_hint::opengl_forward_compat(true);
        glfw::window_hint::opengl_profile(glfw::OpenGlCoreProfile);
        glfw::window_hint::resizable(false);

        let window = glfw::Window::create(640, 480,
                                          "MandelRust",
                                          glfw::Windowed)
            .expect("Failed to create GLFW window.");

        window.make_context_current();

        let win_ctrl = ~WindowController::new(&window);

        let engine = MandelEngine::new();
        native::task::spawn( proc() { engine.process(); });

        while !window.should_close() {
            glfw::poll_events();
            for event in window.flush_events() {
                win_ctrl.handle_window_event(&window, event);
            }
        }
    });
}
<|endoftext|>