<commit_before>mod render;

#[cfg(test)]
mod tests;

use std::iter;

use either::Either;
use hir::{HasSource, Semantics};
use ide_db::{
    base_db::FileRange,
    defs::{Definition, IdentClass},
    famous_defs::FamousDefs,
    helpers::pick_best_token,
    FxIndexSet, RootDatabase,
};
use itertools::Itertools;
use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, T};

use crate::{
    doc_links::token_as_doc_comment,
    markup::Markup,
    runnables::{runnable_fn, runnable_mod},
    FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, TryToNav,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HoverConfig {
    pub links_in_hover: bool,
    pub documentation: Option<HoverDocFormat>,
}

impl HoverConfig {
    fn markdown(&self) -> bool {
        matches!(self.documentation, Some(HoverDocFormat::Markdown))
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HoverDocFormat {
    Markdown,
    PlainText,
}

#[derive(Debug, Clone)]
pub enum HoverAction {
    Runnable(Runnable),
    Implementation(FilePosition),
    Reference(FilePosition),
    GoToType(Vec<HoverGotoTypeData>),
}

impl HoverAction {
    fn goto_type_from_targets(db: &RootDatabase, targets: Vec<hir::ModuleDef>) -> Self {
        let targets = targets
            .into_iter()
            .filter_map(|it| {
                Some(HoverGotoTypeData {
                    mod_path: render::path(
                        db,
                        it.module(db)?,
                        it.name(db).map(|name| name.to_string()),
                    ),
                    nav: it.try_to_nav(db)?,
                })
            })
            .collect();
        HoverAction::GoToType(targets)
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct HoverGotoTypeData {
    pub mod_path: String,
    pub nav: NavigationTarget,
}

/// Contains the results when hovering over an item
#[derive(Debug, Default)]
pub struct HoverResult {
    pub markup: Markup,
    pub actions: Vec<HoverAction>,
}

// Feature: Hover
//
// Shows additional information, like the type of an expression or the documentation for a definition when "focusing" code.
// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.
//
// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[]
pub(crate) fn hover(
    db: &RootDatabase,
    FileRange { file_id, range }: FileRange,
    config: &HoverConfig,
) -> Option<RangeInfo<HoverResult>> {
    let sema = &hir::Semantics::new(db);
    let file = sema.parse(file_id).syntax().clone();

    if !range.is_empty() {
        return hover_ranged(&file, range, sema, config);
    }
    let offset = range.start();

    let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
        IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] | T![Self] => 3,
        T!['('] | T![')'] => 2,
        kind if kind.is_trivia() => 0,
        _ => 1,
    })?;

    if let Some(doc_comment) = token_as_doc_comment(&original_token) {
        cov_mark::hit!(no_highlight_on_comment_hover);
        return doc_comment.get_definition_with_descend_at(sema, offset, |def, node, range| {
            let res = hover_for_definition(sema, file_id, def, &node, config)?;
            Some(RangeInfo::new(range, res))
        });
    }

    let in_attr = matches!(original_token.parent().and_then(ast::TokenTree::cast), Some(tt) if tt.syntax().ancestors().any(|it| ast::Meta::can_cast(it.kind())));
    let descended = if in_attr {
        [sema.descend_into_macros_with_kind_preference(original_token.clone())].into()
    } else {
        sema.descend_into_macros_with_same_text(original_token.clone())
    };

    // FIXME: Definition should include known lints and the like instead of having this special case here
    let hovered_lint = descended.iter().find_map(|token| {
        let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
        render::try_for_lint(&attr, token)
    });
    if let Some(res) = hovered_lint {
        return Some(RangeInfo::new(original_token.text_range(), res));
    }

    let result = descended
        .iter()
        .filter_map(|token| {
            let node = token.parent()?;
            let class = IdentClass::classify_token(sema, token)?;
            Some(class.definitions().into_iter().zip(iter::once(node).cycle()))
        })
        .flatten()
        .unique_by(|&(def, _)| def)
        .filter_map(|(def, node)| hover_for_definition(sema, file_id, def, &node, config))
        .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| {
            acc.actions.extend(actions);
            acc.markup = Markup::from(format!("{}\n---\n{}", acc.markup, markup));
            acc
        });

    if result.is_none() {
        // fallbacks, show keywords or types

        let res = descended.iter().find_map(|token| render::keyword(sema, config, token));
        if let Some(res) = res {
            return Some(RangeInfo::new(original_token.text_range(), res));
        }
        let res = descended
            .iter()
            .find_map(|token| hover_type_fallback(sema, config, token, &original_token));
        if let Some(_) = res {
            return res;
        }
    }
    result.map(|mut res: HoverResult| {
        res.actions = dedupe_or_merge_hover_actions(res.actions);
        RangeInfo::new(original_token.text_range(), res)
    })
}

pub(crate) fn hover_for_definition(
    sema: &Semantics<'_, RootDatabase>,
    file_id: FileId,
    definition: Definition,
    node: &SyntaxNode,
    config: &HoverConfig,
) -> Option<HoverResult> {
    let famous_defs = match &definition {
        Definition::BuiltinType(_) => Some(FamousDefs(sema, sema.scope(node)?.krate())),
        _ => None,
    };
    render::definition(sema.db, definition, famous_defs.as_ref(), config).map(|markup| {
        HoverResult {
            markup: render::process_markup(sema.db, definition, &markup, config),
            actions: show_implementations_action(sema.db, definition)
                .into_iter()
                .chain(show_fn_references_action(sema.db, definition))
                .chain(runnable_action(sema, definition, file_id))
                .chain(goto_type_action_for_def(sema.db, definition))
                .collect(),
        }
    })
}

fn hover_ranged(
    file: &SyntaxNode,
    range: syntax::TextRange,
    sema: &Semantics<'_, RootDatabase>,
    config: &HoverConfig,
) -> Option<RangeInfo<HoverResult>> {
    // FIXME: make this work in attributes
    let expr_or_pat = file.covering_element(range).ancestors().find_map(|it| {
        match_ast! {
            match it {
                ast::Expr(expr) => Some(Either::Left(expr)),
                ast::Pat(pat) => Some(Either::Right(pat)),
                _ => None,
            }
        }
    })?;
    let res = match &expr_or_pat {
        Either::Left(ast::Expr::TryExpr(try_expr)) => render::try_expr(sema, config, try_expr),
        Either::Left(ast::Expr::PrefixExpr(prefix_expr))
            if prefix_expr.op_kind() == Some(ast::UnaryOp::Deref) =>
        {
            render::deref_expr(sema, config, prefix_expr)
        }
        _ => None,
    };
    let res = res.or_else(|| render::type_info(sema, config, &expr_or_pat));
    res.map(|it| {
        let range = match expr_or_pat {
            Either::Left(it) => it.syntax().text_range(),
            Either::Right(it) => it.syntax().text_range(),
        };
        RangeInfo::new(range, it)
    })
}

fn hover_type_fallback(
    sema: &Semantics<'_, RootDatabase>,
    config: &HoverConfig,
    token: &SyntaxToken,
    original_token: &SyntaxToken,
) -> Option<RangeInfo<HoverResult>> {
    let node = token
        .parent_ancestors()
        .take_while(|it| !ast::Item::can_cast(it.kind()))
        .find(|n| ast::Expr::can_cast(n.kind()) || ast::Pat::can_cast(n.kind()))?;

    let expr_or_pat = match_ast! {
        match node {
            ast::Expr(it) => Either::Left(it),
            ast::Pat(it) => Either::Right(it),
            // If this node is a MACRO_CALL, it means that `descend_into_macros_many` failed to resolve.
            // (e.g expanding a builtin macro). So we give up here.
            ast::MacroCall(_it) => return None,
            _ => return None,
        }
    };

    let res = render::type_info(sema, config, &expr_or_pat)?;
    let range = sema
        .original_range_opt(&node)
        .map(|frange| frange.range)
        .unwrap_or_else(|| original_token.text_range());
    Some(RangeInfo::new(range, res))
}

fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
    fn to_action(nav_target: NavigationTarget) -> HoverAction {
        HoverAction::Implementation(FilePosition {
            file_id: nav_target.file_id,
            offset: nav_target.focus_or_full_range().start(),
        })
    }

    let adt = match def {
        Definition::Trait(it) => return it.try_to_nav(db).map(to_action),
        Definition::Adt(it) => Some(it),
        Definition::SelfType(it) => it.self_ty(db).as_adt(),
        _ => None,
    }?;
    adt.try_to_nav(db).map(to_action)
}

fn show_fn_references_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
    match def {
        Definition::Function(it) => it.try_to_nav(db).map(|nav_target| {
            HoverAction::Reference(FilePosition {
                file_id: nav_target.file_id,
                offset: nav_target.focus_or_full_range().start(),
            })
        }),
        _ => None,
    }
}

fn runnable_action(
    sema: &hir::Semantics<'_, RootDatabase>,
    def: Definition,
    file_id: FileId,
) -> Option<HoverAction> {
    match def {
        Definition::Module(it) => runnable_mod(sema, it).map(HoverAction::Runnable),
        Definition::Function(func) => {
            let src = func.source(sema.db)?;
            if src.file_id != file_id.into() {
                cov_mark::hit!(hover_macro_generated_struct_fn_doc_comment);
                cov_mark::hit!(hover_macro_generated_struct_fn_doc_attr);
                return None;
            }

            runnable_fn(sema, func).map(HoverAction::Runnable)
        }
        _ => None,
    }
}

fn goto_type_action_for_def(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
    let mut targets: Vec<hir::ModuleDef> = Vec::new();
    let mut push_new_def = |item: hir::ModuleDef| {
        if !targets.contains(&item) {
            targets.push(item);
        }
    };

    if let Definition::GenericParam(hir::GenericParam::TypeParam(it)) = def {
        it.trait_bounds(db).into_iter().for_each(|it| push_new_def(it.into()));
    } else {
        let ty = match def {
            Definition::Local(it) => it.ty(db),
            Definition::GenericParam(hir::GenericParam::ConstParam(it)) => it.ty(db),
            Definition::Field(field) => field.ty(db),
            Definition::Function(function) => function.ret_type(db),
            _ => return None,
        };

        walk_and_push_ty(db, &ty, &mut push_new_def);
    }

    Some(HoverAction::goto_type_from_targets(db, targets))
}

fn walk_and_push_ty(
    db: &RootDatabase,
    ty: &hir::Type,
    push_new_def: &mut dyn FnMut(hir::ModuleDef),
) {
    ty.walk(db, |t| {
        if let Some(adt) = t.as_adt() {
            push_new_def(adt.into());
        } else if let Some(trait_) = t.as_dyn_trait() {
            push_new_def(trait_.into());
        } else if let Some(traits) = t.as_impl_traits(db) {
            traits.for_each(|it| push_new_def(it.into()));
        } else if let Some(trait_) = t.as_associated_type_parent_trait(db) {
            push_new_def(trait_.into());
        }
    });
}

fn dedupe_or_merge_hover_actions(actions: Vec<HoverAction>) -> Vec<HoverAction> {
    let mut deduped_actions = Vec::with_capacity(actions.len());
    let mut go_to_type_targets = FxIndexSet::default();

    let mut seen_implementation = false;
    let mut seen_reference = false;
    let mut seen_runnable = false;
    for action in actions {
        match action {
            HoverAction::GoToType(targets) => {
                go_to_type_targets.extend(targets);
            }
            HoverAction::Implementation(..) => {
                if !seen_implementation {
                    seen_implementation = true;
                    deduped_actions.push(action);
                }
            }
            HoverAction::Reference(..) => {
                if !seen_reference {
                    seen_reference = true;
                    deduped_actions.push(action);
                }
            }
            HoverAction::Runnable(..) => {
                if !seen_runnable {
                    seen_runnable = true;
                    deduped_actions.push(action);
                }
            }
        };
    }

    if !go_to_type_targets.is_empty() {
        deduped_actions.push(HoverAction::GoToType(go_to_type_targets.into_iter().collect()));
    }

    deduped_actions
}
<commit_msg>on hover fallback error, adds ast::type as possible node<commit_after>mod render;

#[cfg(test)]
mod tests;

use std::iter;

use either::Either;
use hir::{HasSource, Semantics};
use ide_db::{
    base_db::FileRange,
    defs::{Definition, IdentClass},
    famous_defs::FamousDefs,
    helpers::pick_best_token,
    FxIndexSet, RootDatabase,
};
use itertools::Itertools;
use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, T};

use crate::{
    doc_links::token_as_doc_comment,
    markup::Markup,
    runnables::{runnable_fn, runnable_mod},
    FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, TryToNav,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HoverConfig {
    pub links_in_hover: bool,
    pub documentation: Option<HoverDocFormat>,
}

impl HoverConfig {
    fn markdown(&self) -> bool {
        matches!(self.documentation, Some(HoverDocFormat::Markdown))
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HoverDocFormat {
    Markdown,
    PlainText,
}

#[derive(Debug, Clone)]
pub enum HoverAction {
    Runnable(Runnable),
    Implementation(FilePosition),
    Reference(FilePosition),
    GoToType(Vec<HoverGotoTypeData>),
}

impl HoverAction {
    fn goto_type_from_targets(db: &RootDatabase, targets: Vec<hir::ModuleDef>) -> Self {
        let targets = targets
            .into_iter()
            .filter_map(|it| {
                Some(HoverGotoTypeData {
                    mod_path: render::path(
                        db,
                        it.module(db)?,
                        it.name(db).map(|name| name.to_string()),
                    ),
                    nav: it.try_to_nav(db)?,
                })
            })
            .collect();
        HoverAction::GoToType(targets)
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct HoverGotoTypeData {
    pub mod_path: String,
    pub nav: NavigationTarget,
}

/// Contains the results when hovering over an item
#[derive(Debug, Default)]
pub struct HoverResult {
    pub markup: Markup,
    pub actions: Vec<HoverAction>,
}

// Feature: Hover
//
// Shows additional information, like the type of an expression or the documentation for a definition when "focusing" code.
// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.
//
// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[]
pub(crate) fn hover(
    db: &RootDatabase,
    FileRange { file_id, range }: FileRange,
    config: &HoverConfig,
) -> Option<RangeInfo<HoverResult>> {
    let sema = &hir::Semantics::new(db);
    let file = sema.parse(file_id).syntax().clone();

    if !range.is_empty() {
        return hover_ranged(&file, range, sema, config);
    }
    let offset = range.start();

    let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
        IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] | T![Self] => 3,
        T!['('] | T![')'] => 2,
        kind if kind.is_trivia() => 0,
        _ => 1,
    })?;

    if let Some(doc_comment) = token_as_doc_comment(&original_token) {
        cov_mark::hit!(no_highlight_on_comment_hover);
        return doc_comment.get_definition_with_descend_at(sema, offset, |def, node, range| {
            let res = hover_for_definition(sema, file_id, def, &node, config)?;
            Some(RangeInfo::new(range, res))
        });
    }

    let in_attr = matches!(original_token.parent().and_then(ast::TokenTree::cast), Some(tt) if tt.syntax().ancestors().any(|it| ast::Meta::can_cast(it.kind())));
    let descended = if in_attr {
        [sema.descend_into_macros_with_kind_preference(original_token.clone())].into()
    } else {
        sema.descend_into_macros_with_same_text(original_token.clone())
    };

    // FIXME: Definition should include known lints and the like instead of having this special case here
    let hovered_lint = descended.iter().find_map(|token| {
        let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
        render::try_for_lint(&attr, token)
    });
    if let Some(res) = hovered_lint {
        return Some(RangeInfo::new(original_token.text_range(), res));
    }

    let result = descended
        .iter()
        .filter_map(|token| {
            let node = token.parent()?;
            let class = IdentClass::classify_token(sema, token)?;
            Some(class.definitions().into_iter().zip(iter::once(node).cycle()))
        })
        .flatten()
        .unique_by(|&(def, _)| def)
        .filter_map(|(def, node)| hover_for_definition(sema, file_id, def, &node, config))
        .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| {
            acc.actions.extend(actions);
            acc.markup = Markup::from(format!("{}\n---\n{}", acc.markup, markup));
            acc
        });

    if result.is_none() {
        // fallbacks, show keywords or types

        let res = descended.iter().find_map(|token| render::keyword(sema, config, token));
        if let Some(res) = res {
            return Some(RangeInfo::new(original_token.text_range(), res));
        }
        let res = descended
            .iter()
            .find_map(|token| hover_type_fallback(sema, config, token, &original_token));
        if let Some(_) = res {
            return res;
        }
    }
    result.map(|mut res: HoverResult| {
        res.actions = dedupe_or_merge_hover_actions(res.actions);
        RangeInfo::new(original_token.text_range(), res)
    })
}

pub(crate) fn hover_for_definition(
    sema: &Semantics<'_, RootDatabase>,
    file_id: FileId,
    definition: Definition,
    node: &SyntaxNode,
    config: &HoverConfig,
) -> Option<HoverResult> {
    let famous_defs = match &definition {
        Definition::BuiltinType(_) => Some(FamousDefs(sema, sema.scope(node)?.krate())),
        _ => None,
    };
    render::definition(sema.db, definition, famous_defs.as_ref(), config).map(|markup| {
        HoverResult {
            markup: render::process_markup(sema.db, definition, &markup, config),
            actions: show_implementations_action(sema.db, definition)
                .into_iter()
                .chain(show_fn_references_action(sema.db, definition))
                .chain(runnable_action(sema, definition, file_id))
                .chain(goto_type_action_for_def(sema.db, definition))
                .collect(),
        }
    })
}

fn hover_ranged(
    file: &SyntaxNode,
    range: syntax::TextRange,
    sema: &Semantics<'_, RootDatabase>,
    config: &HoverConfig,
) -> Option<RangeInfo<HoverResult>> {
    // FIXME: make this work in attributes
    let expr_or_pat = file.covering_element(range).ancestors().find_map(|it| {
        match_ast! {
            match it {
                ast::Expr(expr) => Some(Either::Left(expr)),
                ast::Pat(pat) => Some(Either::Right(pat)),
                _ => None,
            }
        }
    })?;
    let res = match &expr_or_pat {
        Either::Left(ast::Expr::TryExpr(try_expr)) => render::try_expr(sema, config, try_expr),
        Either::Left(ast::Expr::PrefixExpr(prefix_expr))
            if prefix_expr.op_kind() == Some(ast::UnaryOp::Deref) =>
        {
            render::deref_expr(sema, config, prefix_expr)
        }
        _ => None,
    };
    let res = res.or_else(|| render::type_info(sema, config, &expr_or_pat));
    res.map(|it| {
        let range = match expr_or_pat {
            Either::Left(it) => it.syntax().text_range(),
            Either::Right(it) => it.syntax().text_range(),
        };
        RangeInfo::new(range, it)
    })
}

fn hover_type_fallback(
    sema: &Semantics<'_, RootDatabase>,
    config: &HoverConfig,
    token: &SyntaxToken,
    original_token: &SyntaxToken,
) -> Option<RangeInfo<HoverResult>> {
    let node = token
        .parent_ancestors()
        .take_while(|it| !ast::Item::can_cast(it.kind()))
        .find(|n| {
            ast::Expr::can_cast(n.kind())
                || ast::Pat::can_cast(n.kind())
                || ast::Type::can_cast(n.kind())
        })?;

    let expr_or_pat = match_ast! {
        match node {
            ast::Expr(it) => Either::Left(it),
            ast::Pat(it) => Either::Right(it),
            // If this node is a MACRO_CALL, it means that `descend_into_macros_many` failed to resolve.
            // (e.g expanding a builtin macro). So we give up here.
            ast::MacroCall(_it) => return None,
            _ => return None,
        }
    };

    let res = render::type_info(sema, config, &expr_or_pat)?;
    let range = sema
        .original_range_opt(&node)
        .map(|frange| frange.range)
        .unwrap_or_else(|| original_token.text_range());
    Some(RangeInfo::new(range, res))
}

fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
    fn to_action(nav_target: NavigationTarget) -> HoverAction {
        HoverAction::Implementation(FilePosition {
            file_id: nav_target.file_id,
            offset: nav_target.focus_or_full_range().start(),
        })
    }

    let adt = match def {
        Definition::Trait(it) => return it.try_to_nav(db).map(to_action),
        Definition::Adt(it) => Some(it),
        Definition::SelfType(it) => it.self_ty(db).as_adt(),
        _ => None,
    }?;
    adt.try_to_nav(db).map(to_action)
}

fn show_fn_references_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
    match def {
        Definition::Function(it) => it.try_to_nav(db).map(|nav_target| {
            HoverAction::Reference(FilePosition {
                file_id: nav_target.file_id,
                offset: nav_target.focus_or_full_range().start(),
            })
        }),
        _ => None,
    }
}

fn runnable_action(
    sema: &hir::Semantics<'_, RootDatabase>,
    def: Definition,
    file_id: FileId,
) -> Option<HoverAction> {
    match def {
        Definition::Module(it) => runnable_mod(sema, it).map(HoverAction::Runnable),
        Definition::Function(func) => {
            let src = func.source(sema.db)?;
            if src.file_id != file_id.into() {
                cov_mark::hit!(hover_macro_generated_struct_fn_doc_comment);
                cov_mark::hit!(hover_macro_generated_struct_fn_doc_attr);
                return None;
            }

            runnable_fn(sema, func).map(HoverAction::Runnable)
        }
        _ => None,
    }
}

fn goto_type_action_for_def(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
    let mut targets: Vec<hir::ModuleDef> = Vec::new();
    let mut push_new_def = |item: hir::ModuleDef| {
        if !targets.contains(&item) {
            targets.push(item);
        }
    };

    if let Definition::GenericParam(hir::GenericParam::TypeParam(it)) = def {
        it.trait_bounds(db).into_iter().for_each(|it| push_new_def(it.into()));
    } else {
        let ty = match def {
            Definition::Local(it) => it.ty(db),
            Definition::GenericParam(hir::GenericParam::ConstParam(it)) => it.ty(db),
            Definition::Field(field) => field.ty(db),
            Definition::Function(function) => function.ret_type(db),
            _ => return None,
        };

        walk_and_push_ty(db, &ty, &mut push_new_def);
    }

    Some(HoverAction::goto_type_from_targets(db, targets))
}

fn walk_and_push_ty(
    db: &RootDatabase,
    ty: &hir::Type,
    push_new_def: &mut dyn FnMut(hir::ModuleDef),
) {
    ty.walk(db, |t| {
        if let Some(adt) = t.as_adt() {
            push_new_def(adt.into());
        } else if let Some(trait_) = t.as_dyn_trait() {
            push_new_def(trait_.into());
        } else if let Some(traits) = t.as_impl_traits(db) {
            traits.for_each(|it| push_new_def(it.into()));
        } else if let Some(trait_) = t.as_associated_type_parent_trait(db) {
            push_new_def(trait_.into());
        }
    });
}

fn dedupe_or_merge_hover_actions(actions: Vec<HoverAction>) -> Vec<HoverAction> {
    let mut deduped_actions = Vec::with_capacity(actions.len());
    let mut go_to_type_targets = FxIndexSet::default();

    let mut seen_implementation = false;
    let mut seen_reference = false;
    let mut seen_runnable = false;
    for action in actions {
        match action {
            HoverAction::GoToType(targets) => {
                go_to_type_targets.extend(targets);
            }
            HoverAction::Implementation(..) => {
                if !seen_implementation {
                    seen_implementation = true;
                    deduped_actions.push(action);
                }
            }
            HoverAction::Reference(..) => {
                if !seen_reference {
                    seen_reference = true;
                    deduped_actions.push(action);
                }
            }
            HoverAction::Runnable(..) => {
                if !seen_runnable {
                    seen_runnable = true;
                    deduped_actions.push(action);
                }
            }
        };
    }

    if !go_to_type_targets.is_empty() {
        deduped_actions.push(HoverAction::GoToType(go_to_type_targets.into_iter().collect()));
    }

    deduped_actions
}
<|endoftext|>


<commit_before>use std::{
    fs,
    collections::HashMap,
    path::{Path, PathBuf},
    process::{Command, Output, Stdio},
    io::{Error, ErrorKind}
};

use failure::bail;
use itertools::Itertools;

pub use teraron::{Mode, Overwrite, Verify};

pub type Result<T> = std::result::Result<T, failure::Error>;

pub const GRAMMAR: &str = "crates/ra_syntax/src/grammar.ron";
const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar";
const OK_INLINE_TESTS_DIR: &str = "crates/ra_syntax/tests/data/parser/inline/ok";
const ERR_INLINE_TESTS_DIR: &str = "crates/ra_syntax/tests/data/parser/inline/err";

pub const SYNTAX_KINDS: &str = "crates/ra_parser/src/syntax_kind/generated.rs.tera";
pub const AST: &str = "crates/ra_syntax/src/ast/generated.rs.tera";
const TOOLCHAIN: &str = "stable";

#[derive(Debug)]
pub struct Test {
    pub name: String,
    pub text: String,
    pub ok: bool,
}

pub fn collect_tests(s: &str) -> Vec<(usize, Test)> {
    let mut res = vec![];
    let prefix = "// ";
    let comment_blocks = s
        .lines()
        .map(str::trim_start)
        .enumerate()
        .group_by(|(_idx, line)| line.starts_with(prefix));

    'outer: for (is_comment, block) in comment_blocks.into_iter() {
        if !is_comment {
            continue;
        }
        let mut block = block.map(|(idx, line)| (idx, &line[prefix.len()..]));

        let mut ok = true;
        let (start_line, name) = loop {
            match block.next() {
                Some((idx, line)) if line.starts_with("test ") => {
                    break (idx, line["test ".len()..].to_string());
                }
                Some((idx, line)) if line.starts_with("test_err ") => {
                    ok = false;
                    break (idx, line["test_err ".len()..].to_string());
                }
                Some(_) => (),
                None => continue 'outer,
            }
        };
        let text: String =
            itertools::join(block.map(|(_, line)| line).chain(::std::iter::once("")), "\n");
        assert!(!text.trim().is_empty() && text.ends_with('\n'));
        res.push((start_line, Test { name, text, ok }))
    }
    res
}

pub fn generate(mode: Mode) -> Result<()> {
    let grammar = project_root().join(GRAMMAR);
    let syntax_kinds = project_root().join(SYNTAX_KINDS);
    let ast = project_root().join(AST);
    teraron::generate(&syntax_kinds, &grammar, mode)?;
    teraron::generate(&ast, &grammar, mode)?;
    Ok(())
}

pub fn project_root() -> PathBuf {
    Path::new(&env!("CARGO_MANIFEST_DIR")).ancestors().nth(2).unwrap().to_path_buf()
}

pub fn run(cmdline: &str, dir: &str) -> Result<()> {
    do_run(cmdline, dir, |c| {
        c.stdout(Stdio::inherit());
    })
    .map(|_| ())
}

pub fn run_with_output(cmdline: &str, dir: &str) -> Result<Output> {
    do_run(cmdline, dir, |_| {})
}

pub fn run_rustfmt(mode: Mode) -> Result<()> {
    match Command::new("rustup")
        .args(&["run", TOOLCHAIN, "--", "cargo", "fmt", "--version"])
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
    {
        Ok(status) if status.success() => (),
        _ => install_rustfmt()?,
    };

    if mode == Verify {
        run(&format!("rustup run {} -- cargo fmt -- --check", TOOLCHAIN), ".")?;
    } else {
        run(&format!("rustup run {} -- cargo fmt", TOOLCHAIN), ".")?;
    }
    Ok(())
}

pub fn install_rustfmt() -> Result<()> {
    run(&format!("rustup install {}", TOOLCHAIN), ".")?;
    run(&format!("rustup component add rustfmt --toolchain {}", TOOLCHAIN), ".")
}

pub fn install_format_hook() -> Result<()> {
    let result_path = Path::new(if cfg!(windows) {
        "./.git/hooks/pre-commit.exe"
    } else {
        "./.git/hooks/pre-commit"
    });
    if !result_path.exists() {
        run("cargo build --package tools --bin pre-commit", ".")?;
        if cfg!(windows) {
            fs::copy("./target/debug/pre-commit.exe", result_path)?;
        } else {
            fs::copy("./target/debug/pre-commit", result_path)?;
        }
    } else {
        return Err(Error::new(ErrorKind::AlreadyExists, "Git hook already created").into());
    }
    Ok(())
}

pub fn run_clippy() -> Result<()> {
    match Command::new("rustup")
        .args(&["run", TOOLCHAIN, "--", "cargo", "clippy", "--version"])
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
    {
        Ok(status) if status.success() => (),
        _ => install_clippy()?,
    };

    let allowed_lints = [
        "clippy::collapsible_if",
        "clippy::nonminimal_bool",
        "clippy::needless_pass_by_value",
        "clippy::map_clone", // FIXME: remove when Iterator::copied stabilizes (1.36.0)
    ];
    run(
        &format!(
            "rustup run {} -- cargo clippy --all-features --all-targets -- -A {}",
            TOOLCHAIN,
            allowed_lints.join(" -A ")
        ),
        ".",
    )?;
    Ok(())
}

pub fn install_clippy() -> Result<()> {
    run(&format!("rustup install {}", TOOLCHAIN), ".")?;
    run(&format!("rustup component add clippy --toolchain {}", TOOLCHAIN), ".")
}

pub fn run_fuzzer() -> Result<()> {
    match Command::new("cargo")
        .args(&["fuzz", "--help"])
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
    {
        Ok(status) if status.success() => (),
        _ => run("cargo install cargo-fuzz", ".")?,
    };

    run("rustup run nightly -- cargo fuzz run parser", "./crates/ra_syntax")
}

pub fn gen_tests(mode: Mode) -> Result<()> {
    let tests = tests_from_dir(&project_root().join(Path::new(GRAMMAR_DIR)))?;
    fn install_tests(tests: &HashMap<String, Test>, into: &str, mode: Mode) -> Result<()> {
        let tests_dir = project_root().join(into);
        if !tests_dir.is_dir() {
            fs::create_dir_all(&tests_dir)?;
        }
        // ok is never actually read, but it needs to be specified to create a Test in existing_tests
        let existing = existing_tests(&tests_dir, true)?;
        for t in existing.keys().filter(|&t| !tests.contains_key(t)) {
            panic!("Test is deleted: {}", t);
        }

        let mut new_idx = existing.len() + 1;
        for (name, test) in tests {
            let path = match existing.get(name) {
                Some((path, _test)) => path.clone(),
                None => {
                    let file_name = format!("{:04}_{}.rs", new_idx, name);
                    new_idx += 1;
                    tests_dir.join(file_name)
                }
            };
            teraron::update(&path, &test.text, mode)?;
        }
        Ok(())
    }
    install_tests(&tests.ok, OK_INLINE_TESTS_DIR, mode)?;
    install_tests(&tests.err, ERR_INLINE_TESTS_DIR, mode)
}

fn do_run<F>(cmdline: &str, dir: &str, mut f: F) -> Result<Output>
where
    F: FnMut(&mut Command),
{
    eprintln!("\nwill run: {}", cmdline);
    let proj_dir = project_root().join(dir);
    let mut args = cmdline.split_whitespace();
    let exec = args.next().unwrap();
    let mut cmd = Command::new(exec);
    f(cmd.args(args).current_dir(proj_dir).stderr(Stdio::inherit()));
    let output = cmd.output()?;
    if !output.status.success() {
        bail!("`{}` exited with {}", cmdline, output.status);
    }
    Ok(output)
}

#[derive(Default, Debug)]
struct Tests {
    pub ok: HashMap<String, Test>,
    pub err: HashMap<String, Test>,
}

fn tests_from_dir(dir: &Path) -> Result<Tests> {
    let mut res = Tests::default();
    for entry in ::walkdir::WalkDir::new(dir) {
        let entry = entry.unwrap();
        if !entry.file_type().is_file() {
            continue;
        }
        if entry.path().extension().unwrap_or_default() != "rs" {
            continue;
        }
        process_file(&mut res, entry.path())?;
    }
    let grammar_rs = dir.parent().unwrap().join("grammar.rs");
    process_file(&mut res, &grammar_rs)?;
    return Ok(res);
    fn process_file(res: &mut Tests, path: &Path) -> Result<()> {
        let text = fs::read_to_string(path)?;

        for (_, test) in collect_tests(&text) {
            if test.ok {
                if let Some(old_test) = res.ok.insert(test.name.clone(), test) {
                    bail!("Duplicate test: {}", old_test.name)
                }
            } else {
                if let Some(old_test) = res.err.insert(test.name.clone(), test) {
                    bail!("Duplicate test: {}", old_test.name)
                }
            }
        }
        Ok(())
    }
}

fn existing_tests(dir: &Path, ok: bool) -> Result<HashMap<String, (PathBuf, Test)>> {
    let mut res = HashMap::new();
    for file in fs::read_dir(dir)? {
        let file = file?;
        let path = file.path();
        if path.extension().unwrap_or_default() != "rs" {
            continue;
        }
        let name = {
            let file_name = path.file_name().unwrap().to_str().unwrap();
            file_name[5..file_name.len() - 3].to_string()
        };
        let text = fs::read_to_string(&path)?;
        let test = Test { name: name.clone(), text, ok };
        if let Some(old) = res.insert(name, (path, test)) {
            println!("Duplicate test: {:?}", old);
        }
    }
    Ok(res)
}
<commit_msg>Allow clippy::redundant_pattern_matching<commit_after>use std::{
    fs,
    collections::HashMap,
    path::{Path, PathBuf},
    process::{Command, Output, Stdio},
    io::{Error, ErrorKind}
};

use failure::bail;
use itertools::Itertools;

pub use teraron::{Mode, Overwrite, Verify};

pub type Result<T> = std::result::Result<T, failure::Error>;

pub const GRAMMAR: &str = "crates/ra_syntax/src/grammar.ron";
const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar";
const OK_INLINE_TESTS_DIR: &str = "crates/ra_syntax/tests/data/parser/inline/ok";
const ERR_INLINE_TESTS_DIR: &str = "crates/ra_syntax/tests/data/parser/inline/err";

pub const SYNTAX_KINDS: &str = "crates/ra_parser/src/syntax_kind/generated.rs.tera";
pub const AST: &str = "crates/ra_syntax/src/ast/generated.rs.tera";
const TOOLCHAIN: &str = "stable";

#[derive(Debug)]
pub struct Test {
    pub name: String,
    pub text: String,
    pub ok: bool,
}

pub fn collect_tests(s: &str) -> Vec<(usize, Test)> {
    let mut res = vec![];
    let prefix = "// ";
    let comment_blocks = s
        .lines()
        .map(str::trim_start)
        .enumerate()
        .group_by(|(_idx, line)| line.starts_with(prefix));

    'outer: for (is_comment, block) in comment_blocks.into_iter() {
        if !is_comment {
            continue;
        }
        let mut block = block.map(|(idx, line)| (idx, &line[prefix.len()..]));

        let mut ok = true;
        let (start_line, name) = loop {
            match block.next() {
                Some((idx, line)) if line.starts_with("test ") => {
                    break (idx, line["test ".len()..].to_string());
                }
                Some((idx, line)) if line.starts_with("test_err ") => {
                    ok = false;
                    break (idx, line["test_err ".len()..].to_string());
                }
                Some(_) => (),
                None => continue 'outer,
            }
        };
        let text: String =
            itertools::join(block.map(|(_, line)| line).chain(::std::iter::once("")), "\n");
        assert!(!text.trim().is_empty() && text.ends_with('\n'));
        res.push((start_line, Test { name, text, ok }))
    }
    res
}

pub fn generate(mode: Mode) -> Result<()> {
    let grammar = project_root().join(GRAMMAR);
    let syntax_kinds = project_root().join(SYNTAX_KINDS);
    let ast = project_root().join(AST);
    teraron::generate(&syntax_kinds, &grammar, mode)?;
    teraron::generate(&ast, &grammar, mode)?;
    Ok(())
}

pub fn project_root() -> PathBuf {
    Path::new(&env!("CARGO_MANIFEST_DIR")).ancestors().nth(2).unwrap().to_path_buf()
}

pub fn run(cmdline: &str, dir: &str) -> Result<()> {
    do_run(cmdline, dir, |c| {
        c.stdout(Stdio::inherit());
    })
    .map(|_| ())
}

pub fn run_with_output(cmdline: &str, dir: &str) -> Result<Output> {
    do_run(cmdline, dir, |_| {})
}

pub fn run_rustfmt(mode: Mode) -> Result<()> {
    match Command::new("rustup")
        .args(&["run", TOOLCHAIN, "--", "cargo", "fmt", "--version"])
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
    {
        Ok(status) if status.success() => (),
        _ => install_rustfmt()?,
    };

    if mode == Verify {
        run(&format!("rustup run {} -- cargo fmt -- --check", TOOLCHAIN), ".")?;
    } else {
        run(&format!("rustup run {} -- cargo fmt", TOOLCHAIN), ".")?;
    }
    Ok(())
}

pub fn install_rustfmt() -> Result<()> {
    run(&format!("rustup install {}", TOOLCHAIN), ".")?;
    run(&format!("rustup component add rustfmt --toolchain {}", TOOLCHAIN), ".")
}

pub fn install_format_hook() -> Result<()> {
    let result_path = Path::new(if cfg!(windows) {
        "./.git/hooks/pre-commit.exe"
    } else {
        "./.git/hooks/pre-commit"
    });
    if !result_path.exists() {
        run("cargo build --package tools --bin pre-commit", ".")?;
        if cfg!(windows) {
            fs::copy("./target/debug/pre-commit.exe", result_path)?;
        } else {
            fs::copy("./target/debug/pre-commit", result_path)?;
        }
    } else {
        return Err(Error::new(ErrorKind::AlreadyExists, "Git hook already created").into());
    }
    Ok(())
}

pub fn run_clippy() -> Result<()> {
    match Command::new("rustup")
        .args(&["run", TOOLCHAIN, "--", "cargo", "clippy", "--version"])
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
    {
        Ok(status) if status.success() => (),
        _ => install_clippy()?,
    };

    let allowed_lints = [
        "clippy::collapsible_if",
        "clippy::map_clone", // FIXME: remove when Iterator::copied stabilizes (1.36.0)
        "clippy::needless_pass_by_value",
        "clippy::nonminimal_bool",
        "clippy::redundant_pattern_matching",
    ];
    run(
        &format!(
            "rustup run {} -- cargo clippy --all-features --all-targets -- -A {}",
            TOOLCHAIN,
            allowed_lints.join(" -A ")
        ),
        ".",
    )?;
    Ok(())
}

pub fn install_clippy() -> Result<()> {
    run(&format!("rustup install {}", TOOLCHAIN), ".")?;
    run(&format!("rustup component add clippy --toolchain {}", TOOLCHAIN), ".")
}

pub fn run_fuzzer() -> Result<()> {
    match Command::new("cargo")
        .args(&["fuzz", "--help"])
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
    {
        Ok(status) if status.success() => (),
        _ => run("cargo install cargo-fuzz", ".")?,
    };

    run("rustup run nightly -- cargo fuzz run parser", "./crates/ra_syntax")
}

pub fn gen_tests(mode: Mode) -> Result<()> {
    let tests = tests_from_dir(&project_root().join(Path::new(GRAMMAR_DIR)))?;
    fn install_tests(tests: &HashMap<String, Test>, into: &str, mode: Mode) -> Result<()> {
        let tests_dir = project_root().join(into);
        if !tests_dir.is_dir() {
            fs::create_dir_all(&tests_dir)?;
        }
        // ok is never actually read, but it needs to be specified to create a Test in existing_tests
        let existing = existing_tests(&tests_dir, true)?;
        for t in existing.keys().filter(|&t| !tests.contains_key(t)) {
            panic!("Test is deleted: {}", t);
        }

        let mut new_idx = existing.len() + 1;
        for (name, test) in tests {
            let path = match existing.get(name) {
                Some((path, _test)) => path.clone(),
                None => {
                    let file_name = format!("{:04}_{}.rs", new_idx, name);
                    new_idx += 1;
                    tests_dir.join(file_name)
                }
            };
            teraron::update(&path, &test.text, mode)?;
        }
        Ok(())
    }
    install_tests(&tests.ok, OK_INLINE_TESTS_DIR, mode)?;
    install_tests(&tests.err, ERR_INLINE_TESTS_DIR, mode)
}

fn do_run<F>(cmdline: &str, dir: &str, mut f: F) -> Result<Output>
where
    F: FnMut(&mut Command),
{
    eprintln!("\nwill run: {}", cmdline);
    let proj_dir = project_root().join(dir);
    let mut args = cmdline.split_whitespace();
    let exec = args.next().unwrap();
    let mut cmd = Command::new(exec);
    f(cmd.args(args).current_dir(proj_dir).stderr(Stdio::inherit()));
    let output = cmd.output()?;
    if !output.status.success() {
        bail!("`{}` exited with {}", cmdline, output.status);
    }
    Ok(output)
}

#[derive(Default, Debug)]
struct Tests {
    pub ok: HashMap<String, Test>,
    pub err: HashMap<String, Test>,
}

fn tests_from_dir(dir: &Path) -> Result<Tests> {
    let mut res = Tests::default();
    for entry in ::walkdir::WalkDir::new(dir) {
        let entry = entry.unwrap();
        if !entry.file_type().is_file() {
            continue;
        }
        if entry.path().extension().unwrap_or_default() != "rs" {
            continue;
        }
        process_file(&mut res, entry.path())?;
    }
    let grammar_rs = dir.parent().unwrap().join("grammar.rs");
    process_file(&mut res, &grammar_rs)?;
    return Ok(res);
    fn process_file(res: &mut Tests, path: &Path) -> Result<()> {
        let text = fs::read_to_string(path)?;

        for (_, test) in collect_tests(&text) {
            if test.ok {
                if let Some(old_test) = res.ok.insert(test.name.clone(), test) {
                    bail!("Duplicate test: {}", old_test.name)
                }
            } else {
                if let Some(old_test) = res.err.insert(test.name.clone(), test) {
                    bail!("Duplicate test: {}", old_test.name)
                }
            }
        }
        Ok(())
    }
}

fn existing_tests(dir: &Path, ok: bool) -> Result<HashMap<String, (PathBuf, Test)>> {
    let mut res = HashMap::new();
    for file in fs::read_dir(dir)? {
        let file = file?;
        let path = file.path();
        if path.extension().unwrap_or_default() != "rs" {
            continue;
        }
        let name = {
            let file_name = path.file_name().unwrap().to_str().unwrap();
            file_name[5..file_name.len() - 3].to_string()
        };
        let text = fs::read_to_string(&path)?;
        let test = Test { name: name.clone(), text, ok };
        if let Some(old) = res.insert(name, (path, test)) {
            println!("Duplicate test: {:?}", old);
        }
    }
    Ok(res)
}
<|endoftext|>


<commit_before>// `cargo test --benches` and `#[feature(test)]` work only in nightly
#![cfg(rustc_nightly)]
#![cfg(feature = "bytes")]
#![feature(test)]

extern crate test;

use std::fmt::Write;

use bytes::Bytes;
use perftest_bytes::messages;
use protobuf::MessageFull;

fn make_string_of_len(len: usize) -> String {
    let mut s = String::new();
    for i in 0..len {
        write!(s, "{}", i % 10).expect("unreachable");
    }
    s
}

fn make_repeated(len: usize) -> Vec<u8> {
    let mut m = messages::TestMessage::new();

    for i in 0..100 {
        m.sr.push(make_string_of_len(i % len));
        m.br.push(make_string_of_len((i + len / 2) % len).into_bytes());
    }

    m.write_to_bytes().expect("write")
}

#[bench]
fn parse_repeated_small_regular(b: &mut test::Bencher) {
    let bs = make_repeated(30);
    b.iter(|| messages::TestMessage::parse_from_bytes(&bs).expect("parse"))
}

#[bench]
fn parse_repeated_small_bytes(b: &mut test::Bencher) {
    let bs = Bytes::from(make_repeated(30));
    b.iter(|| messages::TestMessageWithBytes::parse_from_tokio_bytes(&bs).expect("parse"))
}

#[bench]
fn parse_repeated_medium_regular(b: &mut test::Bencher) {
    let bs = make_repeated(300);
    b.iter(|| messages::TestMessage::parse_from_bytes(&bs).expect("parse"))
}

#[bench]
fn parse_repeated_medium_bytes(b: &mut test::Bencher) {
    let bs = Bytes::from(make_repeated(300));
    b.iter(|| messages::TestMessageWithBytes::parse_from_tokio_bytes(&bs).expect("parse"))
}

#[bench]
fn parse_repeated_large_regular(b: &mut test::Bencher) {
    let bs = make_repeated(3000);
    b.iter(|| messages::TestMessage::parse_from_bytes(&bs).expect("parse"))
}

#[bench]
fn parse_repeated_large_bytes(b: &mut test::Bencher) {
    let bs = Bytes::from(make_repeated(3000));
    b.iter(|| messages::TestMessageWithBytes::parse_from_tokio_bytes(&bs).expect("parse"))
}

#[bench]
fn parse_repeated_huge_regular(b: &mut test::Bencher) {
    let bs = make_repeated(30000);
    b.iter(|| messages::TestMessage::parse_from_bytes(&bs).expect("parse"))
}

#[bench]
fn parse_repeated_huge_bytes(b: &mut test::Bencher) {
    let bs = Bytes::from(make_repeated(30000));
    b.iter(|| messages::TestMessageWithBytes::parse_from_tokio_bytes(&bs).expect("parse"))
}
<commit_msg>Fix compilation on nightly<commit_after>// `cargo test --benches` and `#[feature(test)]` work only in nightly
#![cfg(rustc_nightly)]
#![cfg(feature = "bytes")]
#![feature(test)]

extern crate test;

use bytes::Bytes;
use perftest_bytes::messages;
use protobuf::Message;

fn make_string_of_len(len: usize) -> String {
    let mut s = String::new();
    for i in 0..len {
        write!(s, "{}", i % 10).expect("unreachable");
    }
    s
}

fn make_repeated(len: usize) -> Vec<u8> {
    let mut m = messages::TestMessage::new();

    for i in 0..100 {
        m.sr.push(make_string_of_len(i % len));
        m.br.push(make_string_of_len((i + len / 2) % len).into_bytes());
    }

    m.write_to_bytes().expect("write")
}

#[bench]
fn parse_repeated_small_regular(b: &mut test::Bencher) {
    let bs = make_repeated(30);
    b.iter(|| messages::TestMessage::parse_from_bytes(&bs).expect("parse"))
}

#[bench]
fn parse_repeated_small_bytes(b: &mut test::Bencher) {
    let bs = Bytes::from(make_repeated(30));
    b.iter(|| messages::TestMessageWithBytes::parse_from_tokio_bytes(&bs).expect("parse"))
}

#[bench]
fn parse_repeated_medium_regular(b: &mut test::Bencher) {
    let bs = make_repeated(300);
    b.iter(|| messages::TestMessage::parse_from_bytes(&bs).expect("parse"))
}

#[bench]
fn parse_repeated_medium_bytes(b: &mut test::Bencher) {
    let bs = Bytes::from(make_repeated(300));
    b.iter(|| messages::TestMessageWithBytes::parse_from_tokio_bytes(&bs).expect("parse"))
}

#[bench]
fn parse_repeated_large_regular(b: &mut test::Bencher) {
    let bs = make_repeated(3000);
    b.iter(|| messages::TestMessage::parse_from_bytes(&bs).expect("parse"))
}

#[bench]
fn parse_repeated_large_bytes(b: &mut test::Bencher) {
    let bs = Bytes::from(make_repeated(3000));
    b.iter(|| messages::TestMessageWithBytes::parse_from_tokio_bytes(&bs).expect("parse"))
}

#[bench]
fn parse_repeated_huge_regular(b: &mut test::Bencher) {
    let bs = make_repeated(30000);
    b.iter(|| messages::TestMessage::parse_from_bytes(&bs).expect("parse"))
}

#[bench]
fn parse_repeated_huge_bytes(b: &mut test::Bencher) {
    let bs = Bytes::from(make_repeated(30000));
    b.iter(|| messages::TestMessageWithBytes::parse_from_tokio_bytes(&bs).expect("parse"))
}
<|endoftext|>


<commit_before>use cortex_m;
use stm32f0x2::{USART1 as UART1, GPIOA, NVIC, RCC};
use stm32f0x2::interrupt::*;

const FREQUENCY: u32 = 48000000;

static mut DATA_UART1: u16 = 0;

pub enum NBits {
    _8bits,
    _9bits,
}

pub enum StopBits {
    _0b5,
    _1b,
    _1b5,
    _2b,
}

pub enum Parity {
    /// No Parity
    None,
    /// Even Parity
    Even,
    /// Odd Parity
    Odd,
}

interrupt!(USART1, receive);

pub fn init(baudrate: u32, nbits: NBits, nbstopbits: StopBits, parity: Parity) {
    cortex_m::interrupt::free(|cs| {
        let rcc = RCC.borrow(cs);
        let gpioa = GPIOA.borrow(cs);
        let uart1 = UART1.borrow(cs);
        let nvic = NVIC.borrow(cs);

        // Enable GPIOA Clock
        rcc.ahbenr.write(|w| w.iopaen().enabled());
        // Enable USART1 Clock
        rcc.apb2enr.write(|w| w.usart1en().enabled());

        // Configure PA9/PA10 Alternate Function 1 -> USART1
        gpioa.ospeedr.write(|w| {
            w.ospeedr9().high_speed().ospeedr10().high_speed()
        });
        gpioa.pupdr.write(
            |w| w.pupdr9().pull_up().pupdr10().pull_up(),
        );
        gpioa.afrh.write(|w| w.afrh9().af1().afrh10().af1());
        gpioa.moder.write(
            |w| w.moder9().alternate().moder10().alternate(),
        );
        gpioa.otyper.write(
            |w| w.ot9().push_pull().ot10().push_pull(),
        );

        // Configure UART : Word length
        match nbits {
            NBits::_8bits => {
                uart1.cr1.modify(|_, w| w.m()._8bits());
            }
            NBits::_9bits => {
                uart1.cr1.modify(|_, w| w.m()._9bits());
            }
        }
        // Configure UART : Parity
        match parity {
            Parity::None => {
                uart1.cr1.modify(|_, w| w.pce().disabled());
            }
            Parity::Even => {
                uart1.cr1.modify(|_, w| w.pce().enabled());
                uart1.cr1.modify(|_, w| w.ps().even());
            }
            Parity::Odd => {
                uart1.cr1.modify(|_, w| w.pce().enabled());
                uart1.cr1.modify(|_, w| w.ps().odd());
            }
        }
        // Configure UART : Transfert Direction - Oversampling - RX Interrupt
        uart1.cr1.modify(|_, w| {
            w.te()
                .enabled()
                .re()
                .enabled()
                .over8()
                .over8()
                .rxneie()
                .enabled()
        });
        // Configure UART : 1 stop bit
        match nbstopbits {
            StopBits::_0b5 => {
                uart1.cr2.modify(|_, w| w.stop().half_stop());
            }
            StopBits::_1b => {
                uart1.cr2.modify(|_, w| w.stop()._1stop());
            }
            StopBits::_1b5 => {
                uart1.cr2.modify(|_, w| w.stop()._1half_stop());
            }
            StopBits::_2b => {
                uart1.cr2.modify(|_, w| w.stop()._2stop());
            }
        }

        // Configure UART : disable hardware flow control - Overrun interrupt
        uart1.cr3.write(|w| {
            w.rtse()
                .disabled()
                .ctse()
                .disabled()
                .ctsie()
                .disabled()
                .ovrdis()
                .disabled()
        });
        // Configure UART : baudrate
        uart1.brr.write(|w| {
            w.div_fraction().bits(
                (FREQUENCY / (baudrate / 2)) as u8 & 0x0F,
            )
        });
        uart1.brr.write(|w| {
            w.div_mantissa().bits(
                ((FREQUENCY / (baudrate / 2)) >> 4) as u16,
            )
        });
        // Configure UART : Asynchronous mode
        uart1.cr2.modify(
            |_, w| w.linen().disabled().clken().disabled(),
        );
        // UART1 enabled
        uart1.cr1.modify(|_, w| w.ue().enabled());
        nvic.enable(Interrupt::USART1);
        nvic.clear_pending(Interrupt::USART1);
    })
}

pub fn send(byte: u8) {
    cortex_m::interrupt::free(|cs| {
        let uart1 = UART1.borrow(cs);
        uart1.tdr.write(|w| w.tdr().bits(byte as u16));
    })
}

pub fn transmit_complete() -> bool {
    cortex_m::interrupt::free(|cs| {
        let uart1 = UART1.borrow(cs);
        if uart1.isr.read().tc().bit_is_set() {
            uart1.icr.write(|w| w.tccf().clear_bit());
            true
        } else {
            false
        }
    })
}

fn receive_callback() {
    cortex_m::interrupt::free(|cs| {
        let uart = UART1.borrow(cs);
        unsafe {
            DATA_UART1 = uart.rdr.read().rdr().bits();
        }
    })
}

pub fn receive() {
    cortex_m::interrupt::free(|cs| {
        let uart = UART1.borrow(cs);
        if uart.isr.read().rxne().bit_is_set() {
            receive_callback();
        }
    })
}
<commit_msg>Update uart with callback at setup.<commit_after>use core;

use cortex_m;
use stm32f0x2::{USART1 as UART1, GPIOA, NVIC, RCC};
use stm32f0x2::interrupt::*;

const FREQUENCY: u32 = 48000000;

static mut DATA_UART1: u16 = 0;

pub enum NBits {
    _8bits,
    _9bits,
}

pub enum StopBits {
    _0b5,
    _1b,
    _1b5,
    _2b,
}

pub enum Parity {
    /// No Parity
    None,
    /// Even Parity
    Even,
    /// Odd Parity
    Odd,
}

interrupt!(USART1, receive);

pub fn setup<F>(baudrate: u32, nbits: NBits, nbstopbits: StopBits, parity: Parity, mut f: F)
where F: FnMut(u8)
 {
    cortex_m::interrupt::free(|cs| {
        let rcc = RCC.borrow(cs);
        let gpioa = GPIOA.borrow(cs);
        let uart1 = UART1.borrow(cs);
        let nvic = NVIC.borrow(cs);

        // Enable GPIOA Clock
        rcc.ahbenr.write(|w| w.iopaen().enabled());
        // Enable USART1 Clock
        rcc.apb2enr.write(|w| w.usart1en().enabled());

        // Configure PA9/PA10 Alternate Function 1 -> USART1
        gpioa.ospeedr.write(|w| {
            w.ospeedr9().high_speed().ospeedr10().high_speed()
        });
        gpioa.pupdr.write(
            |w| w.pupdr9().pull_up().pupdr10().pull_up(),
        );
        gpioa.afrh.write(|w| w.afrh9().af1().afrh10().af1());
        gpioa.moder.write(
            |w| w.moder9().alternate().moder10().alternate(),
        );
        gpioa.otyper.write(
            |w| w.ot9().push_pull().ot10().push_pull(),
        );

        // Configure UART : Word length
        match nbits {
            NBits::_8bits => {
                uart1.cr1.modify(|_, w| w.m()._8bits());
            }
            NBits::_9bits => {
                uart1.cr1.modify(|_, w| w.m()._9bits());
            }
        }
        // Configure UART : Parity
        match parity {
            Parity::None => {
                uart1.cr1.modify(|_, w| w.pce().disabled());
            }
            Parity::Even => {
                uart1.cr1.modify(|_, w| w.pce().enabled());
                uart1.cr1.modify(|_, w| w.ps().even());
            }
            Parity::Odd => {
                uart1.cr1.modify(|_, w| w.pce().enabled());
                uart1.cr1.modify(|_, w| w.ps().odd());
            }
        }
        // Configure UART : Transfert Direction - Oversampling - RX Interrupt
        uart1.cr1.modify(|_, w| {
            w.te()
                .enabled()
                .re()
                .enabled()
                .over8()
                .over8()
                .rxneie()
                .enabled()
        });
        // Configure UART : 1 stop bit
        match nbstopbits {
            StopBits::_0b5 => {
                uart1.cr2.modify(|_, w| w.stop().half_stop());
            }
            StopBits::_1b => {
                uart1.cr2.modify(|_, w| w.stop()._1stop());
            }
            StopBits::_1b5 => {
                uart1.cr2.modify(|_, w| w.stop()._1half_stop());
            }
            StopBits::_2b => {
                uart1.cr2.modify(|_, w| w.stop()._2stop());
            }
        }

        // Configure UART : disable hardware flow control - Overrun interrupt
        uart1.cr3.write(|w| {
            w.rtse()
                .disabled()
                .ctse()
                .disabled()
                .ctsie()
                .disabled()
                .ovrdis()
                .disabled()
        });
        // Configure UART : baudrate
        uart1.brr.write(|w| {
            w.div_fraction().bits(
                (FREQUENCY / (baudrate / 2)) as u8 & 0x0F,
            )
        });
        uart1.brr.write(|w| {
            w.div_mantissa().bits(
                ((FREQUENCY / (baudrate / 2)) >> 4) as u16,
            )
        });
        // Configure UART : Asynchronous mode
        uart1.cr2.modify(
            |_, w| w.linen().disabled().clken().disabled(),
        );
        // UART1 enabled
        uart1.cr1.modify(|_, w| w.ue().enabled());
        nvic.enable(Interrupt::USART1);
        nvic.clear_pending(Interrupt::USART1);
    });
    unsafe {
        RECV_CB = Some(extend_lifetime(&mut f));
    }
}

pub fn send(byte: u8) {
    cortex_m::interrupt::free(|cs| {
        let uart1 = UART1.borrow(cs);
        uart1.tdr.write(|w| w.tdr().bits(byte as u16));
    })
}

pub fn transmit_complete() -> bool {
    cortex_m::interrupt::free(|cs| {
        let uart1 = UART1.borrow(cs);
        if uart1.isr.read().tc().bit_is_set() {
            uart1.icr.write(|w| w.tccf().clear_bit());
            true
        } else {
            false
        }
    })
}

static mut RECV_CB: Option<&'static mut FnMut(u8)> = None;

fn receive_callback() {
    cortex_m::interrupt::free(|cs| {
        let uart = UART1.borrow(cs);
        unsafe {
            DATA_UART1 = uart.rdr.read().rdr().bits();
        }
    });
    unsafe {
        if let Some(ref mut cb) = RECV_CB {
            cb(DATA_UART1 as u8);
        }
    }
}

pub fn receive() {
    cortex_m::interrupt::free(|cs| {
        let uart = UART1.borrow(cs);
        if uart.isr.read().rxne().bit_is_set() {
            receive_callback();
        }
    })
}

unsafe fn extend_lifetime<'a>(f: &'a mut FnMut(u8)) -> &'static mut FnMut(u8) {
    core::mem::transmute::<&'a mut FnMut(u8), &'static mut FnMut(u8)>(f)
}
<|endoftext|>


<commit_before>mod carbon;
mod cf;
mod cg;

use self::{
    carbon::{
        kTISPropertyUnicodeKeyLayoutData, LMGetKbdType, TISCopyCurrentKeyboardInputSource,
        TISCopyCurrentKeyboardLayoutInputSource, TISGetInputSourceProperty, UCKeyAction,
        UCKeyTranslate, UCKeyTranslateBits,
    },
    cf::{
        kCFAllocatorDefault, kCFRunLoopDefaultMode, CFDataGetBytePtr,
        CFMachPortCreateRunLoopSource, CFRelease, CFRunLoopAddSource, CFRunLoopContainsSource,
        CFRunLoopGetCurrent, CFRunLoopRemoveSource, CFRunLoopRun,
    },
    cg::{
        CGEventTapCreate, EventMask, EventRef, EventTapLocation, EventTapOptions,
        EventTapPlacement, EventTapProxy, EventType,
    },
};
use crate::KeyCode;
use cg::EventField;
use parking_lot::Mutex;
use std::{
    collections::{hash_map::Entry, HashMap},
    ffi::c_void,
    sync::{mpsc::channel, Arc},
    thread,
};

#[derive(Debug, snafu::Snafu)]
pub enum Error {
    AlreadyRegistered,
    NotRegistered,
    CouldntCreateEventTap,
    CouldntCreateRunLoopSource,
    CouldntGetCurrentRunLoop,
    ThreadStoppedUnexpectedly,
}

pub type Result<T> = std::result::Result<T, Error>;

struct Owned<T>(*mut T);

impl<T> Drop for Owned<T> {
    fn drop(&mut self) {
        unsafe {
            CFRelease(self.0.cast());
        }
    }
}

#[derive(Copy, Clone)]
struct RunLoop(cf::RunLoopRef);

unsafe impl Send for RunLoop {}

type RegisteredKeys = Mutex<HashMap<KeyCode, Box<dyn FnMut() + Send + 'static>>>;

pub struct Hook {
    event_loop: RunLoop,
    hotkeys: Arc<RegisteredKeys>,
}

impl Drop for Hook {
    fn drop(&mut self) {
        unsafe {
            let mode = cf::CFRunLoopCopyCurrentMode(self.event_loop.0);
            if !mode.is_null() {
                cf::CFRelease(mode.cast());
                cf::CFRunLoopStop(self.event_loop.0);
            }
        }
    }
}

impl Hook {
    pub fn new() -> Result<Self> {
        let hotkeys = Arc::new(Mutex::new(HashMap::new()));
        let thread_hotkeys = hotkeys.clone();

        let (sender, receiver) = channel();

        // The code here is mostly based on:
        // https://github.com/kwhat/libuiohook/blob/f4bb19be8aee7d7ee5ead89b5a89dbf440e2a71a/src/darwin/input_hook.c#L1086

        thread::spawn(move || unsafe {
            let hotkeys_ptr: *const Mutex<_> = &*thread_hotkeys;

            let port = CGEventTapCreate(
                EventTapLocation::Session,
                EventTapPlacement::HeadInsertEventTap,
                EventTapOptions::DefaultTap,
                EventMask::KEY_DOWN,
                Some(callback),
                hotkeys_ptr as *mut c_void,
            );
            if port.is_null() {
                let _ = sender.send(Err(Error::CouldntCreateEventTap));
                return;
            }
            let port = Owned(port);

            let source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, port.0, 0);
            if source.is_null() {
                let _ = sender.send(Err(Error::CouldntCreateRunLoopSource));
                return;
            }
            let source = Owned(source);

            let event_loop = CFRunLoopGetCurrent();
            if event_loop.is_null() {
                let _ = sender.send(Err(Error::CouldntGetCurrentRunLoop));
                return;
            }

            CFRunLoopAddSource(event_loop, source.0, kCFRunLoopDefaultMode);

            if { sender }.send(Ok(RunLoop(event_loop))).is_ok() {
                CFRunLoopRun();
            }

            if CFRunLoopContainsSource(event_loop, source.0, kCFRunLoopDefaultMode) {
                CFRunLoopRemoveSource(event_loop, source.0, kCFRunLoopDefaultMode);
            }
        });

        let event_loop = receiver
            .recv()
            .map_err(|_| Error::ThreadStoppedUnexpectedly)??;

        Ok(Hook {
            event_loop,
            hotkeys,
        })
    }

    pub fn register<F>(&self, hotkey: KeyCode, callback: F) -> Result<()>
    where
        F: FnMut() + Send + 'static,
    {
        if let Entry::Vacant(vacant) = self.hotkeys.lock().entry(hotkey) {
            vacant.insert(Box::new(callback));
            Ok(())
        } else {
            Err(Error::AlreadyRegistered)
        }
    }

    pub fn unregister(&self, hotkey: KeyCode) -> Result<()> {
        if self.hotkeys.lock().remove(&hotkey).is_some() {
            Ok(())
        } else {
            Err(Error::NotRegistered)
        }
    }
}

unsafe extern "C" fn callback(
    _: EventTapProxy,
    ty: EventType,
    event: EventRef,
    user_info: *mut c_void,
) -> EventRef {
    if matches!(ty, EventType::KeyDown) {
        let key_code = cg::CGEventGetIntegerValueField(event, EventField::KeyboardEventKeycode);
        let key_code = match key_code {
            0x00 => KeyCode::KeyA,
            0x01 => KeyCode::KeyS,
            0x02 => KeyCode::KeyD,
            0x03 => KeyCode::KeyF,
            0x04 => KeyCode::KeyH,
            0x05 => KeyCode::KeyG,
            0x06 => KeyCode::KeyZ,
            0x07 => KeyCode::KeyX,
            0x08 => KeyCode::KeyC,
            0x09 => KeyCode::KeyV,
            0x0A => KeyCode::IntlBackslash,
            0x0B => KeyCode::KeyB,
            0x0C => KeyCode::KeyQ,
            0x0D => KeyCode::KeyW,
            0x0E => KeyCode::KeyE,
            0x0F => KeyCode::KeyR,
            0x10 => KeyCode::KeyY,
            0x11 => KeyCode::KeyT,
            0x12 => KeyCode::Digit1,
            0x13 => KeyCode::Digit2,
            0x14 => KeyCode::Digit3,
            0x15 => KeyCode::Digit4,
            0x16 => KeyCode::Digit6,
            0x17 => KeyCode::Digit5,
            0x18 => KeyCode::Equal,
            0x19 => KeyCode::Digit9,
            0x1A => KeyCode::Digit7,
            0x1B => KeyCode::Minus,
            0x1C => KeyCode::Digit8,
            0x1D => KeyCode::Digit0,
            0x1E => KeyCode::BracketRight,
            0x1F => KeyCode::KeyO,
            0x20 => KeyCode::KeyU,
            0x21 => KeyCode::BracketLeft,
            0x22 => KeyCode::KeyI,
            0x23 => KeyCode::KeyP,
            0x24 => KeyCode::Enter,
            0x25 => KeyCode::KeyL,
            0x26 => KeyCode::KeyJ,
            0x27 => KeyCode::Quote,
            0x28 => KeyCode::KeyK,
            0x29 => KeyCode::Semicolon,
            0x2A => KeyCode::Backslash,
            0x2B => KeyCode::Comma,
            0x2C => KeyCode::Slash,
            0x2D => KeyCode::KeyN,
            0x2E => KeyCode::KeyM,
            0x2F => KeyCode::Period,
            0x30 => KeyCode::Tab,
            0x31 => KeyCode::Space,
            0x32 => KeyCode::Backquote,
            0x33 => KeyCode::Backspace,
            0x34 => KeyCode::NumpadEnter, // Not Chrome
            0x35 => KeyCode::Escape,
            0x36 => KeyCode::MetaRight,
            0x37 => KeyCode::MetaLeft,
            0x38 => KeyCode::ShiftLeft,
            0x39 => KeyCode::CapsLock,
            0x3A => KeyCode::AltLeft,
            0x3B => KeyCode::ControlLeft,
            0x3C => KeyCode::ShiftRight,
            0x3D => KeyCode::AltRight,
            0x3E => KeyCode::ControlRight,
            0x3F => KeyCode::Fn, // Not Chrome
            0x40 => KeyCode::F17,
            0x41 => KeyCode::NumpadDecimal,
            0x43 => KeyCode::NumpadMultiply,
            0x45 => KeyCode::NumpadAdd,
            0x47 => KeyCode::NumLock,
            0x48 => KeyCode::AudioVolumeUp,
            0x49 => KeyCode::AudioVolumeDown,
            0x4A => KeyCode::AudioVolumeMute,
            0x4B => KeyCode::NumpadDivide,
            0x4C => KeyCode::NumpadEnter,
            0x4E => KeyCode::NumpadSubtract,
            0x4F => KeyCode::F18,
            0x50 => KeyCode::F19,
            0x51 => KeyCode::NumpadEqual,
            0x52 => KeyCode::Numpad0,
            0x53 => KeyCode::Numpad1,
            0x54 => KeyCode::Numpad2,
            0x55 => KeyCode::Numpad3,
            0x56 => KeyCode::Numpad4,
            0x57 => KeyCode::Numpad5,
            0x58 => KeyCode::Numpad6,
            0x59 => KeyCode::Numpad7,
            0x5A => KeyCode::F20,
            0x5B => KeyCode::Numpad8,
            0x5C => KeyCode::Numpad9,
            0x5D => KeyCode::IntlYen,
            0x5E => KeyCode::IntlRo,
            0x5F => KeyCode::NumpadComma,
            0x60 => KeyCode::F5,
            0x61 => KeyCode::F6,
            0x62 => KeyCode::F7,
            0x63 => KeyCode::F3,
            0x64 => KeyCode::F8,
            0x65 => KeyCode::F9,
            0x66 => KeyCode::Lang2,
            0x67 => KeyCode::F11,
            0x68 => KeyCode::Lang1, // KanaMode in Safari
            0x69 => KeyCode::F13,
            0x6A => KeyCode::F16,
            0x6B => KeyCode::F14,
            0x6D => KeyCode::F10,
            0x6E => KeyCode::ContextMenu, // Missing on MDN
            0x6F => KeyCode::F12,
            0x71 => KeyCode::F15,
            // `Help` sometimes replaces the `Insert` key on mac keyboards, Chrome prefers `Insert`.
            0x72 => KeyCode::Help,
            0x73 => KeyCode::Home,
            0x74 => KeyCode::PageUp,
            0x75 => KeyCode::Delete,
            0x76 => KeyCode::F4,
            0x77 => KeyCode::End,
            0x78 => KeyCode::F2,
            0x79 => KeyCode::PageDown,
            0x7A => KeyCode::F1,
            0x7B => KeyCode::ArrowLeft,
            0x7C => KeyCode::ArrowRight,
            0x7D => KeyCode::ArrowDown,
            0x7E => KeyCode::ArrowUp,
            _ => return event,
        };

        let hotkeys = user_info as *const RegisteredKeys;
        let hotkeys = &*hotkeys;
        if let Some(callback) = hotkeys.lock().get_mut(&key_code) {
            callback();
        }
    }
    event
}

pub(crate) fn try_resolve(key_code: KeyCode) -> Option<String> {
    unsafe {
        let current_keyboard_raw = TISCopyCurrentKeyboardInputSource();
        if current_keyboard_raw.is_null() {
            return None;
        }
        let mut current_keyboard = Owned(current_keyboard_raw);

        let mut layout_data =
            TISGetInputSourceProperty(current_keyboard.0, kTISPropertyUnicodeKeyLayoutData);

        if layout_data.is_null() {
            let current_keyboard_raw = TISCopyCurrentKeyboardLayoutInputSource();
            if current_keyboard_raw.is_null() {
                return None;
            }
            current_keyboard = Owned(current_keyboard_raw);

            layout_data =
                TISGetInputSourceProperty(current_keyboard.0, kTISPropertyUnicodeKeyLayoutData);
            if layout_data.is_null() {
                return None;
            }
        }

        let keyboard_layout = CFDataGetBytePtr(layout_data.cast());

        let key_code = match key_code {
            KeyCode::Backquote => 0x32,
            KeyCode::Backslash => 0x2A,
            KeyCode::Backspace => 0x33,
            KeyCode::BracketLeft => 0x21,
            KeyCode::BracketRight => 0x1E,
            KeyCode::Comma => 0x2B,
            KeyCode::Digit0 => 0x1D,
            KeyCode::Digit1 => 0x12,
            KeyCode::Digit2 => 0x13,
            KeyCode::Digit3 => 0x14,
            KeyCode::Digit4 => 0x15,
            KeyCode::Digit5 => 0x17,
            KeyCode::Digit6 => 0x16,
            KeyCode::Digit7 => 0x1A,
            KeyCode::Digit8 => 0x1C,
            KeyCode::Digit9 => 0x19,
            KeyCode::Equal => 0x18,
            KeyCode::IntlBackslash => 0x0A,
            KeyCode::IntlRo => 0x5E,
            KeyCode::IntlYen => 0x5D,
            KeyCode::KeyA => 0x00,
            KeyCode::KeyB => 0x0B,
            KeyCode::KeyC => 0x08,
            KeyCode::KeyD => 0x02,
            KeyCode::KeyE => 0x0E,
            KeyCode::KeyF => 0x03,
            KeyCode::KeyG => 0x05,
            KeyCode::KeyH => 0x04,
            KeyCode::KeyI => 0x22,
            KeyCode::KeyJ => 0x26,
            KeyCode::KeyK => 0x28,
            KeyCode::KeyL => 0x25,
            KeyCode::KeyM => 0x2E,
            KeyCode::KeyN => 0x2D,
            KeyCode::KeyO => 0x1F,
            KeyCode::KeyP => 0x23,
            KeyCode::KeyQ => 0x0C,
            KeyCode::KeyR => 0x0F,
            KeyCode::KeyS => 0x01,
            KeyCode::KeyT => 0x11,
            KeyCode::KeyU => 0x20,
            KeyCode::KeyV => 0x09,
            KeyCode::KeyW => 0x0D,
            KeyCode::KeyX => 0x07,
            KeyCode::KeyY => 0x10,
            KeyCode::KeyZ => 0x06,
            KeyCode::Minus => 0x1B,
            KeyCode::Period => 0x2F,
            KeyCode::Quote => 0x27,
            KeyCode::Semicolon => 0x29,
            KeyCode::Slash => 0x2C,
            _ => return None,
        };

        let mut chars = [0; 4];
        let mut len = 0;

        UCKeyTranslate(
            keyboard_layout.cast(),
            key_code,
            UCKeyAction::Display as _,
            0,
            LMGetKbdType() as _,
            UCKeyTranslateBits::NO_DEAD_KEYS_BIT.bits(),
            &mut 0,
            4,
            &mut len,
            chars.as_mut_ptr(),
        );

        if len == 0 {
            return None;
        }

        String::from_utf16(&chars[..len as usize]).ok()
    }
}
<commit_msg>Don't repeat the hotkeys on macOS<commit_after>mod carbon;
mod cf;
mod cg;

use self::{
    carbon::{
        kTISPropertyUnicodeKeyLayoutData, LMGetKbdType, TISCopyCurrentKeyboardInputSource,
        TISCopyCurrentKeyboardLayoutInputSource, TISGetInputSourceProperty, UCKeyAction,
        UCKeyTranslate, UCKeyTranslateBits,
    },
    cf::{
        kCFAllocatorDefault, kCFRunLoopDefaultMode, CFDataGetBytePtr,
        CFMachPortCreateRunLoopSource, CFRelease, CFRunLoopAddSource, CFRunLoopContainsSource,
        CFRunLoopGetCurrent, CFRunLoopRemoveSource, CFRunLoopRun,
    },
    cg::{
        CGEventTapCreate, EventMask, EventRef, EventTapLocation, EventTapOptions,
        EventTapPlacement, EventTapProxy, EventType,
    },
};
use crate::KeyCode;
use cg::EventField;
use parking_lot::Mutex;
use std::{
    collections::{hash_map::Entry, HashMap},
    ffi::c_void,
    sync::{mpsc::channel, Arc},
    thread,
};

#[derive(Debug, snafu::Snafu)]
pub enum Error {
    AlreadyRegistered,
    NotRegistered,
    CouldntCreateEventTap,
    CouldntCreateRunLoopSource,
    CouldntGetCurrentRunLoop,
    ThreadStoppedUnexpectedly,
}

pub type Result<T> = std::result::Result<T, Error>;

struct Owned<T>(*mut T);

impl<T> Drop for Owned<T> {
    fn drop(&mut self) {
        unsafe {
            CFRelease(self.0.cast());
        }
    }
}

#[derive(Copy, Clone)]
struct RunLoop(cf::RunLoopRef);

unsafe impl Send for RunLoop {}

type RegisteredKeys = Mutex<HashMap<KeyCode, Box<dyn FnMut() + Send + 'static>>>;

pub struct Hook {
    event_loop: RunLoop,
    hotkeys: Arc<RegisteredKeys>,
}

impl Drop for Hook {
    fn drop(&mut self) {
        unsafe {
            let mode = cf::CFRunLoopCopyCurrentMode(self.event_loop.0);
            if !mode.is_null() {
                cf::CFRelease(mode.cast());
                cf::CFRunLoopStop(self.event_loop.0);
            }
        }
    }
}

impl Hook {
    pub fn new() -> Result<Self> {
        let hotkeys = Arc::new(Mutex::new(HashMap::new()));
        let thread_hotkeys = hotkeys.clone();

        let (sender, receiver) = channel();

        // The code here is mostly based on:
        // https://github.com/kwhat/libuiohook/blob/f4bb19be8aee7d7ee5ead89b5a89dbf440e2a71a/src/darwin/input_hook.c#L1086

        thread::spawn(move || unsafe {
            let hotkeys_ptr: *const Mutex<_> = &*thread_hotkeys;

            let port = CGEventTapCreate(
                EventTapLocation::Session,
                EventTapPlacement::HeadInsertEventTap,
                EventTapOptions::DefaultTap,
                EventMask::KEY_DOWN,
                Some(callback),
                hotkeys_ptr as *mut c_void,
            );
            if port.is_null() {
                let _ = sender.send(Err(Error::CouldntCreateEventTap));
                return;
            }
            let port = Owned(port);

            let source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, port.0, 0);
            if source.is_null() {
                let _ = sender.send(Err(Error::CouldntCreateRunLoopSource));
                return;
            }
            let source = Owned(source);

            let event_loop = CFRunLoopGetCurrent();
            if event_loop.is_null() {
                let _ = sender.send(Err(Error::CouldntGetCurrentRunLoop));
                return;
            }

            CFRunLoopAddSource(event_loop, source.0, kCFRunLoopDefaultMode);

            if { sender }.send(Ok(RunLoop(event_loop))).is_ok() {
                CFRunLoopRun();
            }

            if CFRunLoopContainsSource(event_loop, source.0, kCFRunLoopDefaultMode) {
                CFRunLoopRemoveSource(event_loop, source.0, kCFRunLoopDefaultMode);
            }
        });

        let event_loop = receiver
            .recv()
            .map_err(|_| Error::ThreadStoppedUnexpectedly)??;

        Ok(Hook {
            event_loop,
            hotkeys,
        })
    }

    pub fn register<F>(&self, hotkey: KeyCode, callback: F) -> Result<()>
    where
        F: FnMut() + Send + 'static,
    {
        if let Entry::Vacant(vacant) = self.hotkeys.lock().entry(hotkey) {
            vacant.insert(Box::new(callback));
            Ok(())
        } else {
            Err(Error::AlreadyRegistered)
        }
    }

    pub fn unregister(&self, hotkey: KeyCode) -> Result<()> {
        if self.hotkeys.lock().remove(&hotkey).is_some() {
            Ok(())
        } else {
            Err(Error::NotRegistered)
        }
    }
}

unsafe extern "C" fn callback(
    _: EventTapProxy,
    ty: EventType,
    event: EventRef,
    user_info: *mut c_void,
) -> EventRef {
    if !matches!(ty, EventType::KeyDown) {
        return event;
    }

    let is_repeating = cg::CGEventGetIntegerValueField(event, EventField::KeyboardEventAutorepeat);
    if is_repeating != 0 {
        return event;
    }

    let key_code = cg::CGEventGetIntegerValueField(event, EventField::KeyboardEventKeycode);
    let key_code = match key_code {
        0x00 => KeyCode::KeyA,
        0x01 => KeyCode::KeyS,
        0x02 => KeyCode::KeyD,
        0x03 => KeyCode::KeyF,
        0x04 => KeyCode::KeyH,
        0x05 => KeyCode::KeyG,
        0x06 => KeyCode::KeyZ,
        0x07 => KeyCode::KeyX,
        0x08 => KeyCode::KeyC,
        0x09 => KeyCode::KeyV,
        0x0A => KeyCode::IntlBackslash,
        0x0B => KeyCode::KeyB,
        0x0C => KeyCode::KeyQ,
        0x0D => KeyCode::KeyW,
        0x0E => KeyCode::KeyE,
        0x0F => KeyCode::KeyR,
        0x10 => KeyCode::KeyY,
        0x11 => KeyCode::KeyT,
        0x12 => KeyCode::Digit1,
        0x13 => KeyCode::Digit2,
        0x14 => KeyCode::Digit3,
        0x15 => KeyCode::Digit4,
        0x16 => KeyCode::Digit6,
        0x17 => KeyCode::Digit5,
        0x18 => KeyCode::Equal,
        0x19 => KeyCode::Digit9,
        0x1A => KeyCode::Digit7,
        0x1B => KeyCode::Minus,
        0x1C => KeyCode::Digit8,
        0x1D => KeyCode::Digit0,
        0x1E => KeyCode::BracketRight,
        0x1F => KeyCode::KeyO,
        0x20 => KeyCode::KeyU,
        0x21 => KeyCode::BracketLeft,
        0x22 => KeyCode::KeyI,
        0x23 => KeyCode::KeyP,
        0x24 => KeyCode::Enter,
        0x25 => KeyCode::KeyL,
        0x26 => KeyCode::KeyJ,
        0x27 => KeyCode::Quote,
        0x28 => KeyCode::KeyK,
        0x29 => KeyCode::Semicolon,
        0x2A => KeyCode::Backslash,
        0x2B => KeyCode::Comma,
        0x2C => KeyCode::Slash,
        0x2D => KeyCode::KeyN,
        0x2E => KeyCode::KeyM,
        0x2F => KeyCode::Period,
        0x30 => KeyCode::Tab,
        0x31 => KeyCode::Space,
        0x32 => KeyCode::Backquote,
        0x33 => KeyCode::Backspace,
        0x34 => KeyCode::NumpadEnter, // Not Chrome
        0x35 => KeyCode::Escape,
        0x36 => KeyCode::MetaRight,
        0x37 => KeyCode::MetaLeft,
        0x38 => KeyCode::ShiftLeft,
        0x39 => KeyCode::CapsLock,
        0x3A => KeyCode::AltLeft,
        0x3B => KeyCode::ControlLeft,
        0x3C => KeyCode::ShiftRight,
        0x3D => KeyCode::AltRight,
        0x3E => KeyCode::ControlRight,
        0x3F => KeyCode::Fn, // Not Chrome
        0x40 => KeyCode::F17,
        0x41 => KeyCode::NumpadDecimal,
        0x43 => KeyCode::NumpadMultiply,
        0x45 => KeyCode::NumpadAdd,
        0x47 => KeyCode::NumLock,
        0x48 => KeyCode::AudioVolumeUp,
        0x49 => KeyCode::AudioVolumeDown,
        0x4A => KeyCode::AudioVolumeMute,
        0x4B => KeyCode::NumpadDivide,
        0x4C => KeyCode::NumpadEnter,
        0x4E => KeyCode::NumpadSubtract,
        0x4F => KeyCode::F18,
        0x50 => KeyCode::F19,
        0x51 => KeyCode::NumpadEqual,
        0x52 => KeyCode::Numpad0,
        0x53 => KeyCode::Numpad1,
        0x54 => KeyCode::Numpad2,
        0x55 => KeyCode::Numpad3,
        0x56 => KeyCode::Numpad4,
        0x57 => KeyCode::Numpad5,
        0x58 => KeyCode::Numpad6,
        0x59 => KeyCode::Numpad7,
        0x5A => KeyCode::F20,
        0x5B => KeyCode::Numpad8,
        0x5C => KeyCode::Numpad9,
        0x5D => KeyCode::IntlYen,
        0x5E => KeyCode::IntlRo,
        0x5F => KeyCode::NumpadComma,
        0x60 => KeyCode::F5,
        0x61 => KeyCode::F6,
        0x62 => KeyCode::F7,
        0x63 => KeyCode::F3,
        0x64 => KeyCode::F8,
        0x65 => KeyCode::F9,
        0x66 => KeyCode::Lang2,
        0x67 => KeyCode::F11,
        0x68 => KeyCode::Lang1, // KanaMode in Safari
        0x69 => KeyCode::F13,
        0x6A => KeyCode::F16,
        0x6B => KeyCode::F14,
        0x6D => KeyCode::F10,
        0x6E => KeyCode::ContextMenu, // Missing on MDN
        0x6F => KeyCode::F12,
        0x71 => KeyCode::F15,
        // `Help` sometimes replaces the `Insert` key on mac keyboards, Chrome prefers `Insert`.
        0x72 => KeyCode::Help,
        0x73 => KeyCode::Home,
        0x74 => KeyCode::PageUp,
        0x75 => KeyCode::Delete,
        0x76 => KeyCode::F4,
        0x77 => KeyCode::End,
        0x78 => KeyCode::F2,
        0x79 => KeyCode::PageDown,
        0x7A => KeyCode::F1,
        0x7B => KeyCode::ArrowLeft,
        0x7C => KeyCode::ArrowRight,
        0x7D => KeyCode::ArrowDown,
        0x7E => KeyCode::ArrowUp,
        _ => return event,
    };

    let hotkeys = user_info as *const RegisteredKeys;
    let hotkeys = &*hotkeys;
    if let Some(callback) = hotkeys.lock().get_mut(&key_code) {
        callback();
    }

    event
}

pub(crate) fn try_resolve(key_code: KeyCode) -> Option<String> {
    unsafe {
        let current_keyboard_raw = TISCopyCurrentKeyboardInputSource();
        if current_keyboard_raw.is_null() {
            return None;
        }
        let mut current_keyboard = Owned(current_keyboard_raw);

        let mut layout_data =
            TISGetInputSourceProperty(current_keyboard.0, kTISPropertyUnicodeKeyLayoutData);

        if layout_data.is_null() {
            let current_keyboard_raw = TISCopyCurrentKeyboardLayoutInputSource();
            if current_keyboard_raw.is_null() {
                return None;
            }
            current_keyboard = Owned(current_keyboard_raw);

            layout_data =
                TISGetInputSourceProperty(current_keyboard.0, kTISPropertyUnicodeKeyLayoutData);
            if layout_data.is_null() {
                return None;
            }
        }

        let keyboard_layout = CFDataGetBytePtr(layout_data.cast());

        let key_code = match key_code {
            KeyCode::Backquote => 0x32,
            KeyCode::Backslash => 0x2A,
            KeyCode::Backspace => 0x33,
            KeyCode::BracketLeft => 0x21,
            KeyCode::BracketRight => 0x1E,
            KeyCode::Comma => 0x2B,
            KeyCode::Digit0 => 0x1D,
            KeyCode::Digit1 => 0x12,
            KeyCode::Digit2 => 0x13,
            KeyCode::Digit3 => 0x14,
            KeyCode::Digit4 => 0x15,
            KeyCode::Digit5 => 0x17,
            KeyCode::Digit6 => 0x16,
            KeyCode::Digit7 => 0x1A,
            KeyCode::Digit8 => 0x1C,
            KeyCode::Digit9 => 0x19,
            KeyCode::Equal => 0x18,
            KeyCode::IntlBackslash => 0x0A,
            KeyCode::IntlRo => 0x5E,
            KeyCode::IntlYen => 0x5D,
            KeyCode::KeyA => 0x00,
            KeyCode::KeyB => 0x0B,
            KeyCode::KeyC => 0x08,
            KeyCode::KeyD => 0x02,
            KeyCode::KeyE => 0x0E,
            KeyCode::KeyF => 0x03,
            KeyCode::KeyG => 0x05,
            KeyCode::KeyH => 0x04,
            KeyCode::KeyI => 0x22,
            KeyCode::KeyJ => 0x26,
            KeyCode::KeyK => 0x28,
            KeyCode::KeyL => 0x25,
            KeyCode::KeyM => 0x2E,
            KeyCode::KeyN => 0x2D,
            KeyCode::KeyO => 0x1F,
            KeyCode::KeyP => 0x23,
            KeyCode::KeyQ => 0x0C,
            KeyCode::KeyR => 0x0F,
            KeyCode::KeyS => 0x01,
            KeyCode::KeyT => 0x11,
            KeyCode::KeyU => 0x20,
            KeyCode::KeyV => 0x09,
            KeyCode::KeyW => 0x0D,
            KeyCode::KeyX => 0x07,
            KeyCode::KeyY => 0x10,
            KeyCode::KeyZ => 0x06,
            KeyCode::Minus => 0x1B,
            KeyCode::Period => 0x2F,
            KeyCode::Quote => 0x27,
            KeyCode::Semicolon => 0x29,
            KeyCode::Slash => 0x2C,
            _ => return None,
        };

        let mut chars = [0; 4];
        let mut len = 0;

        UCKeyTranslate(
            keyboard_layout.cast(),
            key_code,
            UCKeyAction::Display as _,
            0,
            LMGetKbdType() as _,
            UCKeyTranslateBits::NO_DEAD_KEYS_BIT.bits(),
            &mut 0,
            4,
            &mut len,
            chars.as_mut_ptr(),
        );

        if len == 0 {
            return None;
        }

        String::from_utf16(&chars[..len as usize]).ok()
    }
}
<|endoftext|>