code-blocks 0.3.0

A library to re-order your code
Documentation
use code_blocks::{Block, BlockTree};

use tree_sitter::{Language, Parser, Point, Tree};

pub fn build_tree(text: &str, language: Language) -> Tree {
    let mut parser = Parser::new();
    parser
        .set_language(language)
        .expect("failed to set language");
    parser.parse(text, None).expect("failed to parse text")
}

#[allow(dead_code)]
pub fn copy_item_above<'tree>(
    ident: &str,
    text: &str,
    trees: &Vec<BlockTree<'tree>>,
) -> Option<Block<'tree>> {
    let pos = text
        .lines()
        .enumerate()
        .find_map(|(row, line)| line.find(ident).map(|col| Point::new(row - 1, col)))?;

    for tree in trees {
        if tree.block.tail().start_position() == pos {
            return Some(tree.block.clone());
        }

        if let Some(node) = copy_item_above(ident, text, &tree.children) {
            return Some(node);
        }
    }

    None
}

#[macro_export]
macro_rules! snapshot {
    ($name:tt, $lang:expr, $queries:expr, $input:literal) => {
        #[test]
        fn $name() {
            let text = $input;
            let queries = $queries;

            let force = false;
            fn check_fn(_a: &Block, _b: &Block) -> Result<()> {
                Ok(())
            }

            let tree = build_tree(text, $lang);

            let items = code_blocks::get_query_subtrees(&queries, &tree, text);
            let src_block = copy_item_above("^src", text, &items).expect("failed to find src item");
            let dst_item = copy_item_above("^dst", text, &items);
            let fail_item = copy_item_above("^fail", text, &items);

            let snapshot = if let Some(dst_item) = dst_item {
                let (new_text, mut new_src_start, mut new_dst_start) =
                    code_blocks::move_block(src_block, dst_item, text, Some(check_fn), force)
                        .expect("move block failed");

                let mut new_lines = vec![];
                let mut added_src = false;
                let mut added_dst = false;
                for line in new_text.lines() {
                    new_lines.push(line.to_string());
                    if new_src_start > line.len() {
                        new_src_start -= line.len() + 1;
                    } else if !added_src {
                        new_lines.push(" ".repeat(new_src_start) + "^ Source");
                        added_src = true;
                    }

                    if new_dst_start > line.len() {
                        new_dst_start -= line.len() + 1;
                    } else if !added_dst {
                        new_lines.push(" ".repeat(new_dst_start) + "^ Dest");
                        added_dst = true;
                    }
                }

                let new_text = new_lines.join("\n");
                format!("input:\n{}\n---\noutput:\n{}", text, new_text)
            } else if let Some(fail_item) = fail_item {
                let err =
                    code_blocks::move_block(src_block, fail_item, text, Some(check_fn), force)
                        .err()
                        .expect("move block succeeded, but should've failed");
                format!("input:\n{}\n---\noutput:\n{:?}", text, err)
            } else {
                unreachable!("no dst/fail item in input");
            };

            insta::assert_display_snapshot!(snapshot);
        }
    };

    ($name:tt, $lang:expr, $queries:expr, $check_fn:ident, $input:literal) => {
        #[test]
        fn $name() {
            let text = $input;
            let queries = $queries;

            let force = false;

            let tree = build_tree(text, $lang);

            let items = code_blocks::get_query_subtrees(&queries, &tree, text);
            let src_block = copy_item_above("^src", text, &items).expect("failed to find src item");
            let dst_item = copy_item_above("^dst", text, &items);
            let fail_item = copy_item_above("^fail", text, &items);

            let snapshot = if let Some(dst_item) = dst_item {
                let (new_text, mut new_src_start, mut new_dst_start) =
                    code_blocks::move_block(src_block, dst_item, text, Some($check_fn), force)
                        .expect("move block failed");

                let mut new_lines = vec![];
                let mut added_src = false;
                let mut added_dst = false;
                for line in new_text.lines() {
                    new_lines.push(line.to_string());
                    if new_src_start > line.len() {
                        new_src_start -= line.len() + 1;
                    } else if !added_src {
                        new_lines.push(" ".repeat(new_src_start) + "^ Source");
                        added_src = true;
                    }

                    if new_dst_start > line.len() {
                        new_dst_start -= line.len() + 1;
                    } else if !added_dst {
                        new_lines.push(" ".repeat(new_dst_start) + "^ Dest");
                        added_dst = true;
                    }
                }

                let new_text = new_lines.join("\n");
                format!("input:\n{}\n---\noutput:\n{}", text, new_text)
            } else if let Some(fail_item) = fail_item {
                let err =
                    code_blocks::move_block(src_block, fail_item, text, Some($check_fn), force)
                        .err()
                        .expect("move block succeeded, but should've failed");
                format!("input:\n{}\n---\noutput:\n{:?}", text, err)
            } else {
                unreachable!("no dst/fail item in input");
            };

            insta::assert_display_snapshot!(snapshot);
        }
    };
}