Skip to content

Commit

Permalink
Support lists with TODO items ([ ], [x])
Browse files Browse the repository at this point in the history
  • Loading branch information
Terr committed Nov 11, 2024
1 parent 1442bff commit c733707
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 17 deletions.
1 change: 1 addition & 0 deletions src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub const PREFIX_HEADER: &str = "=== ";
pub const PREFIX_LIST_CONTINUATION: &str = " ";
pub const PREFIX_PREFORMATTED: &str = "| ";
pub const PREFIX_QUOTE: &str = "> ";
pub const PREFIX_TODO_ITEM: &str = "[";

pub const MARKER_FENCED_FILETYPE_BACKTICK: &str = "```";
pub const MARKER_FENCED_FILETYPE_TILDE: &str = "~~~";
2 changes: 1 addition & 1 deletion src/formatting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ pub fn format_to_string(document: &Document) -> String {

/// Finds a word boundary (i.e. whitespace after a word) nearest to the maximum line length.
fn find_word_boundary(line: &FormattedLine, max_line_length: usize) -> Option<usize> {
let prefix_length = line.line_type.get_prefix().len();
let prefix_length = line.line_type.get_prefix_length();
if let Some(split_pos) = line
.contents
.chars()
Expand Down
36 changes: 28 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,11 @@ impl RawLine {
self.raw.trim().is_empty()
}

fn is_bullet_point(&self) -> bool {
LineType::from_raw(&self.trimmed) == LineType::ListBulletPoint
fn is_list_item(&self) -> bool {
match LineType::from_raw(&self.trimmed) {
LineType::ListBulletPoint | LineType::ListTodoItem => true,
_ => false,
}
}

fn is_header(&self) -> bool {
Expand Down Expand Up @@ -183,8 +186,11 @@ impl FormattedLine {
}

fn is_list_item(&self) -> bool {
self.line_type == LineType::ListBulletPoint
|| self.line_type == LineType::ListContinuousLine
match self.line_type {
LineType::ListBulletPoint | LineType::ListTodoItem => true,
LineType::ListContinuousLine => true,
_ => false,
}
}

fn num_indent(&self) -> usize {
Expand All @@ -199,23 +205,28 @@ pub enum LineType {
Header,
/// Currently processing a list item that started with a '* '
ListBulletPoint,
/// Currently processing a wrapped line that is part of the previous ListBulletPoint
/// Currently processing a wrapped line that is part of a list started on an earlier line
ListContinuousLine,
/// A line that starts with a '|' is considered to be preformatted, and can be longer than the
/// maximum line length.
/// An item on a TODO list
ListTodoItem,
/// A line that starts with a '|' is considered to be preformatted, and *can* be longer than
/// the maximum line length.
Preformatted,
/// A line that is prefixed with a '>'
Quote,
}

impl LineType {
/// Note that this function cannot determine if a line is a 'continuation line' in a bullet
/// Detects the type of the given `line` by looking at its first characters.
/// Note that this function cannot determine if the line is a 'continuation line' in a bullet
/// point list since that requires knowledge about the line preceding this one.
fn from_raw(line: &str) -> Self {
if line.starts_with(consts::PREFIX_HEADER) {
LineType::Header
} else if line.starts_with(consts::PREFIX_BULLET_POINT) {
LineType::ListBulletPoint
} else if line.starts_with(consts::PREFIX_TODO_ITEM) {
LineType::ListTodoItem
} else if line.starts_with(consts::MARKER_FENCED_FILETYPE_BACKTICK)
|| line.starts_with(consts::MARKER_FENCED_FILETYPE_TILDE)
|| line.starts_with(consts::PREFIX_PREFORMATTED)
Expand All @@ -233,9 +244,18 @@ impl LineType {
Self::Header => consts::PREFIX_HEADER,
Self::ListBulletPoint => consts::PREFIX_BULLET_POINT,
Self::ListContinuousLine => consts::PREFIX_LIST_CONTINUATION,
Self::ListTodoItem => consts::PREFIX_TODO_ITEM,
Self::Preformatted => consts::PREFIX_PREFORMATTED,
Self::Quote => consts::PREFIX_QUOTE,
_ => "",
}
}

fn get_prefix_length(&self) -> usize {
match self {
// '[ ] ' or '[x] '
Self::ListTodoItem => 4,
_ => self.get_prefix().len(),
}
}
}
9 changes: 9 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ mod tests {
assert_equal(&actual, &expected);
}

#[test]
fn format_list_with_todo_items() {
let expected = read_file(Path::new("tests/todo_items.expected")).unwrap();
let first_format = format_file(Path::new("tests/todo_items.input"));
let second_format = format(&first_format);

assert_equal(&second_format, &expected);
}

#[test]
fn wrapping_long_lines() {
let expected = read_file(Path::new("tests/long_lines.expected")).unwrap();
Expand Down
20 changes: 12 additions & 8 deletions src/parsing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ pub fn parse_document(contents: &str) -> Document {
let header = FormattedLine::from_raw(raw_line, indent_level);

document.add_block(Block::new(header));
} else if raw_line.is_bullet_point() {
// This case means that the line is either the start of a new bullet point list, or the
// continuation of one.
} else if raw_line.is_list_item() {
// This case means that the line is either the start of a new list (bullet point or
// TODO items), or the continuation of one.

let current_block = document.last_block_mut();
let indent_level = determine_new_bullet_point_indent(current_block, &raw_line);
Expand Down Expand Up @@ -103,17 +103,21 @@ fn determine_new_header_indent(document: &Document, raw_line: &RawLine) -> usize
}

fn determine_new_bullet_point_indent(current_block: &Block, raw_line: &RawLine) -> usize {
assert!(raw_line.is_bullet_point());
assert!(raw_line.is_list_item());

if let Some(previous_bullet_point) = current_block.find_previous_of(LineType::ListBulletPoint) {
match previous_bullet_point
let previous_list_item = current_block
.find_previous_of(LineType::ListBulletPoint)
.or_else(|| current_block.find_previous_of(LineType::ListTodoItem));

if let Some(previous_list_item) = previous_list_item {
match previous_list_item
.original_raw
.num_indent
.cmp(&raw_line.num_indent)
{
// List item is continuation of the bullet point list at the same level of
// indenting.
Ordering::Equal => previous_bullet_point.indent_level,
Ordering::Equal => previous_list_item.indent_level,

// List item is shifted one or more levels to the left compared to the previous
// bullet point in the list. Find the first line (starting from the last line)
Expand All @@ -125,7 +129,7 @@ fn determine_new_bullet_point_indent(current_block: &Block, raw_line: &RawLine)

// List item is shifted right compared to the previous bullet point. Only one
// level of indenting per line can be added per line.
Ordering::Less => previous_bullet_point.indent_level + 1,
Ordering::Less => previous_list_item.indent_level + 1,
}
} else if let Some(previous_text) = current_block.find_previous_of(LineType::Text) {
previous_text.indent_level
Expand Down
18 changes: 18 additions & 0 deletions tests/todo_items.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
=== Header 1

[ ] Item 1.A
[x] Item 1.B

=== Header 1.1

[ ] Item 1.1.A

=== Header 1.2

[ ] Item 1.2.A

=== Header 2

[ ] Item 2.A
[x] Item 2.A.A
[ ] Item 2.B
18 changes: 18 additions & 0 deletions tests/todo_items.input
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
=== Header 1

[ ] Item 1.A
[x] Item 1.B

=== Header 1.1

[ ] Item 1.1.A

=== Header 1.2

[ ] Item 1.2.A

=== Header 2

[ ] Item 2.A
[x] Item 2.A.A
[ ] Item 2.B

0 comments on commit c733707

Please sign in to comment.