nexedit 0.2.2

A vim-like text editor, with simple shortcuts.
Documentation
use crate::commands::{self, Result};
use crate::errors;
use crate::errors::*;
use crate::models::application::{Application, ClipboardContent, Mode};
use git2;
use regex::Regex;
use std::cmp::Ordering;

pub fn add(app: &mut Application) -> Result {
    let repo = app.repository.as_ref().ok_or("No repository available")?;
    let buffer = app
        .workspace
        .current_buffer
        .as_ref()
        .ok_or(BUFFER_MISSING)?;
    let mut index = repo
        .index()
        .chain_err(|| "Couldn't get the repository index")?;
    let buffer_path = buffer.path.as_ref().ok_or(BUFFER_PATH_MISSING)?;
    let repo_path = repo.workdir().ok_or("No path found for the repository")?;
    let relative_path = buffer_path
        .strip_prefix(repo_path)
        .chain_err(|| "Failed to build a relative buffer path")?;

    index
        .add_path(relative_path)
        .chain_err(|| "Failed to add path to index.")?;
    index.write().chain_err(|| "Failed to write index.")
}

pub fn copy_remote_url(app: &mut Application) -> Result {
    if let Some(ref mut repo) = app.repository {
        let buffer = app
            .workspace
            .current_buffer
            .as_ref()
            .ok_or(BUFFER_MISSING)?;
        let buffer_path = buffer.path.as_ref().ok_or(BUFFER_PATH_MISSING)?;
        let remote = repo
            .find_remote("origin")
            .chain_err(|| "Couldn't find a remote \"origin\"")?;
        let url = remote.url().ok_or("No URL for remote/origin")?;

        let gh_path = get_gh_path(url)?;

        let repo_path = repo.workdir().ok_or("No path found for the repository")?;
        let relative_path = buffer_path
            .strip_prefix(repo_path)
            .chain_err(|| "Failed to build a relative buffer path")?;

        let status = repo
            .status_file(relative_path)
            .chain_err(|| "Couldn't get status info for the specified path")?;
        if status.contains(git2::Status::WT_NEW) || status.contains(git2::Status::INDEX_NEW) {
            bail!("The provided path doesn't exist in the repository");
        }

        let mut revisions = repo
            .revwalk()
            .chain_err(|| "Couldn't build a list of revisions for the repository")?;

        revisions
            .push_head()
            .chain_err(|| "Failed to push HEAD to commit graph.")?;

        let last_oid = revisions
            .next()
            .and_then(|revision| revision.ok())
            .ok_or("Couldn't find a git object ID for this file")?;

        let line_range = match app.mode {
            Mode::SelectLine(ref s) => {
                let line_1 = buffer.cursor.line + 1;
                let line_2 = s.anchor + 1;

                match line_1.cmp(&line_2) {
                    Ordering::Less => format!("#L{}-L{}", line_1, line_2),
                    Ordering::Greater => format!("#L{}-L{}", line_2, line_1),
                    Ordering::Equal => format!("#L{}", line_1),
                }
            }
            _ => String::new(),
        };

        let gh_url = format!(
            "https://github.com/{}/blob/{:?}/{}{}",
            gh_path,
            last_oid,
            relative_path.to_string_lossy(),
            line_range
        );

        app.clipboard
            .set_content(ClipboardContent::Inline(gh_url))?;
    } else {
        bail!("No repository available");
    }

    commands::application::switch_to_normal_mode(app)?;

    Ok(())
}

fn get_gh_path(url: &str) -> errors::Result<&str> {
    lazy_static! {
        static ref REGEX: Regex =
            Regex::new(r"^(?:https://|git@)github.com(?::|/)(.*?)(?:.git)?$").unwrap();
    }
    REGEX
        .captures(url)
        .and_then(|c| c.get(1))
        .map(|c| c.as_str())
        .chain_err(|| "Failed to capture remote repo path")
}

#[test]
fn test_get_gh_path() {
    let cases = [
        ("git@github.com:d3vboi/nexedit.git", "d3vboi/nexedit"),
        ("https://github.com/d3vboi/nexedit.git", "d3vboi/nexedit"),
        ("https://github.com/d3vboi/nexedit", "d3vboi/nexedit"),
    ];

    cases.iter().for_each(|(url, expected_gh_path)| {
        assert_eq!(&get_gh_path(url).unwrap(), expected_gh_path)
    })
}