blob: e16eb24c38306980d443b0136a3a6006ca58d5e8 [file]
use std::collections::VecDeque;
use tree_sitter::TreeCursor;
use crate::{
context::Context,
layouts::{self, KeyboardLayoutType},
parser::parse,
utils::{get_text, lookahead, lookbehind, pad_right, print_indent, sep},
};
fn is_preproc(n: &tree_sitter::Node) -> bool {
n.kind() == "preproc_include"
|| n.kind() == "preproc_ifdef"
|| n.kind() == "preproc_def"
|| n.kind() == "preproc_function_def"
}
fn traverse(
writer: &mut String,
source: &String,
cursor: &mut TreeCursor,
ctx: &Context,
) {
let node = cursor.node();
match node.kind() {
"comment" => {
// Add a newline before the comment if the previous node is not a comment
if lookbehind(cursor).map_or(false, |n| n.kind() != "comment") {
sep(writer);
}
print_indent(writer, ctx);
let comment = get_text(source, cursor);
// Only reformat single line comments, multi line comments are a
// lot tougher to format properly.
match comment.starts_with("//") {
true => {
writer.push_str("// ");
writer.push_str(comment.trim_start_matches("//").trim());
}
false => writer.push_str(comment),
}
writer.push('\n');
}
"preproc_include" => {
cursor.goto_first_child();
print_indent(writer, ctx);
writer.push_str("#include ");
cursor.goto_next_sibling();
writer.push_str(get_text(source, cursor));
writer.push('\n');
cursor.goto_parent();
// Add a newline if this is the last preproc directive
if lookahead(cursor).map_or(false, |n| !is_preproc(&n)) {
writer.push('\n');
}
}
"preproc_def" => {
cursor.goto_first_child();
writer.push_str("#define ");
// Name
cursor.goto_next_sibling();
writer.push_str(get_text(source, cursor));
writer.push(' ');
// Value
cursor.goto_next_sibling();
writer.push_str(get_text(source, cursor));
writer.push('\n');
cursor.goto_parent();
// Add a newline if this is the last preproc directive
if lookahead(cursor).map_or(false, |n| !is_preproc(&n)) {
writer.push('\n');
}
}
"preproc_function_def" => {
cursor.goto_first_child();
writer.push_str("#define ");
// Function and args
for _ in 0..2 {
cursor.goto_next_sibling();
writer.push_str(get_text(source, cursor));
}
writer.push(' ');
// Value
cursor.goto_next_sibling();
writer.push_str(get_text(source, cursor));
writer.push('\n');
cursor.goto_parent();
// Add a newline if this is the last preproc directive
if lookahead(cursor).map_or(false, |n| !is_preproc(&n)) {
writer.push('\n');
}
}
"preproc_ifdef" => {
print_indent(writer, ctx);
// #ifdef
cursor.goto_first_child();
writer.push_str(get_text(source, cursor).trim());
writer.push(' ');
// Name
cursor.goto_next_sibling();
writer.push_str(get_text(source, cursor));
writer.push_str("\n");
// Body
while cursor.goto_next_sibling() {
traverse(writer, &source, cursor, ctx);
}
// Closing
print_indent(writer, ctx);
writer.push_str("#endif\n");
cursor.goto_parent();
// Add a newline if this is the last preproc directive
if lookahead(cursor).map_or(false, |n| !is_preproc(&n)) {
writer.push('\n');
}
}
"labeled_item" => {
cursor.goto_first_child();
print_indent(writer, ctx);
writer.push_str(get_text(source, cursor));
writer.push_str(": ");
while cursor.goto_next_sibling() {
traverse(writer, &source, cursor, ctx);
}
cursor.goto_parent();
}
"node" => {
// If the previous node is a labeled_item, then the labeled_item will
// contain the indentation rather than the node.
if lookbehind(cursor).map_or(false, |n| n.kind() != ":") {
print_indent(writer, ctx);
}
cursor.goto_first_child();
// Node name and opening
let name = get_text(source, cursor);
writer.push_str(name);
writer.push_str(" {\n");
// Node body
while cursor.goto_next_sibling() {
let ctx = ctx.inc(1);
// When we find the keymap node, we need to set the keymap flag
// so we can properly print the binding cells.
let ctx = match name {
"keymap" => ctx.keymap(),
_ => ctx,
};
traverse(writer, &source, cursor, &ctx);
}
// Node closing
print_indent(writer, ctx);
writer.push_str("};\n");
cursor.goto_parent();
}
"property" => {
cursor.goto_first_child();
print_indent(writer, ctx);
let name = get_text(source, cursor);
writer.push_str(name);
cursor.goto_next_sibling();
writer.push_str(" = ");
while cursor.goto_next_sibling() {
// When we are inside a bindings node, we want to increase the
// indentation level and print the bindings according to the
// keyboard layout.
let ctx = match name {
"bindings" => ctx.inc(1).bindings(),
_ => ctx.with_indent(0),
};
match cursor.node().kind() {
"," => writer.push_str(", "),
";" => break,
_ => traverse(writer, &source, cursor, &ctx),
}
}
writer.push_str(";\n");
cursor.goto_parent();
// Add a newline if the next item is a node
if lookahead(cursor).map_or(false, |n| n.kind() == "node") {
writer.push('\n');
}
}
"string_literal" => {
writer.push_str(get_text(source, cursor));
}
"integer_cells" => {
cursor.goto_first_child();
// Keymap bindings are a special snowflake
if ctx.keymap && ctx.bindings {
print_bindings(writer, source, cursor, ctx);
return;
}
writer.push('<');
let mut first = true;
while cursor.goto_next_sibling() {
match cursor.node().kind() {
">" => break,
_ => {
if first {
first = false;
} else {
writer.push(' ');
}
writer.push_str(get_text(source, cursor));
}
}
}
writer.push('>');
cursor.goto_parent();
}
_ => {
if cursor.goto_first_child() {
traverse(writer, &source, cursor, ctx);
while cursor.goto_next_sibling() {
traverse(writer, &source, cursor, &ctx.inc(1));
}
cursor.goto_parent();
}
}
};
}
fn collect_bindings(
cursor: &mut TreeCursor,
source: &String,
ctx: &Context,
) -> VecDeque<String> {
let mut buf: VecDeque<String> = VecDeque::new();
let mut item = String::new();
while cursor.goto_next_sibling() {
match cursor.node().kind() {
">" => break,
_ => {
let text = get_text(source, cursor).trim();
// If this is a new binding, add a new item to the buffer
if !item.is_empty() && text.starts_with("&") {
buf.push_back(item);
item = String::new();
}
// Add a space between each piece of text
if !item.is_empty() {
item.push(' ');
}
// Add the current piece of text to the buffer
item.push_str(text);
}
}
}
// Add the last item to the buffer
buf.push_back(item);
// Move the items from the temporary buffer into a new vector that contains
// the empty key spaces.
ctx.layout
.bindings
.iter()
.map(|is_key| match is_key {
1 => buf.pop_front().unwrap_or(String::new()),
_ => String::new(),
})
.collect()
}
/// Calculate the maximum size of each column in the bindings table.
fn calculate_sizes(buf: &VecDeque<String>, row_size: usize) -> Vec<usize> {
let mut sizes = Vec::new();
for i in 0..row_size {
let mut max = 0;
for j in (i..buf.len()).step_by(row_size) {
let len = buf[j].len();
if len > max {
max = len;
}
}
sizes.push(max);
}
sizes
}
fn print_bindings(
writer: &mut String,
source: &String,
cursor: &mut TreeCursor,
ctx: &Context,
) {
cursor.goto_first_child();
writer.push_str("<");
let buf = collect_bindings(cursor, source, ctx);
let row_size = ctx.layout.row_size();
let sizes = calculate_sizes(&buf, row_size);
buf.iter().enumerate().for_each(|(i, item)| {
let col = i % row_size;
// Add a newline at the start of each row
if col == 0 {
writer.push('\n');
print_indent(writer, ctx);
}
// Don't add padding to the last binding in the row
let padding = match (i + 1) % row_size == 0 {
true => 0,
false => sizes[col] + 3,
};
writer.push_str(&pad_right(&item, padding));
});
// Close the bindings
writer.push('\n');
print_indent(writer, &ctx.dec(1));
writer.push('>');
cursor.goto_parent();
}
pub fn print(source: &String, layout: &KeyboardLayoutType) -> String {
let mut writer = String::new();
let tree = parse(source.clone());
let mut cursor = tree.walk();
let layout = layouts::get_layout(layout);
let ctx =
Context { indent: 0, bindings: false, keymap: false, layout: &layout };
// The first node is the root document node, so we have to traverse all it's
// children with the same indentation level.
cursor.goto_first_child();
traverse(&mut writer, &source, &mut cursor, &ctx);
while cursor.goto_next_sibling() {
traverse(&mut writer, &source, &mut cursor, &ctx);
}
return writer;
}