Skip to content

Commit e840ce7

Browse files
committed
Improve repeating todo cycling fidelity
1 parent f27bd25 commit e840ce7

4 files changed

Lines changed: 125 additions & 23 deletions

File tree

lib/src/org/model/org_headline.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,4 +156,14 @@ class OrgHeadline extends OrgParentNode {
156156
OrgHeadline withoutKeyword() => keyword == null
157157
? this
158158
: OrgHeadline(stars, null, priority, title, rawTitle, tags, trailing);
159+
160+
bool keywordIsEndState(List<OrgTodoStates> todoStates) {
161+
if (keyword == null) return false;
162+
if (keyword!.done) return true;
163+
return todoStates
164+
.where((t) => t.contains(keyword!.value))
165+
.firstOrNull
166+
?.isEndState(keyword!.value) ==
167+
true;
168+
}
159169
}

lib/src/org/model/org_section.dart

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -75,37 +75,47 @@ class OrgSection extends OrgTree {
7575
headline = headline.edit().visit(visitor).commit<OrgHeadline>();
7676
content = content?.edit().visit(visitor).commit<OrgContent>();
7777

78-
if (hasRepeat) {
79-
if (headline.keyword?.done == true) {
80-
finalKeyword = headline.keyword?.value ?? '';
81-
headline = headline.withoutKeyword();
82-
if (todoStates.any((t) => t.todo.isNotEmpty)) {
83-
headline = headline.cycleTodo(todoStates);
84-
}
78+
// TODO(aaron): Handle `org-log-done` = 'time behavior (record DONE time)
79+
if (!hasRepeat) {
80+
return copyWith(
81+
headline: headline,
82+
content: content,
83+
);
84+
}
85+
86+
if (headline.keywordIsEndState(todoStates)) {
87+
finalKeyword = headline.keyword?.value ?? '';
88+
headline = headline.withoutKeyword();
89+
if (todoStates.any((t) => t.todo.isNotEmpty)) {
90+
headline = headline.cycleTodo(todoStates);
8591
}
86-
final indent = level > 0 ? ' ' * (level + 1) : '';
87-
final timestamp =
88-
OrgSimpleTimestamp('[', now.toOrgDate(), now.toOrgTime(), [], ']');
89-
final lastRepeat = OrgProperty(indent, ':LAST_REPEAT:',
90-
OrgContent([OrgPlainText(' '), timestamp]), '\n');
91-
92-
final note = OrgContent([
93-
// See org-log-note-headings
94-
OrgPlainText(
95-
'State ${'"$finalKeyword"'.padRight(12)} from ${'"$previousKeyword"'.padRight(12)}'),
96-
timestamp,
97-
OrgPlainText('\n'),
98-
]);
92+
}
93+
94+
if (finalKeyword == null) {
9995
return copyWith(
10096
headline: headline,
10197
content: content,
102-
).setProperty<OrgSection>(lastRepeat).addLogNote(note);
98+
);
10399
}
104-
// TODO(aaron): Handle `org-log-done` = 'time behavior (record DONE time)
100+
101+
final indent = level > 0 ? ' ' * (level + 1) : '';
102+
final timestamp =
103+
OrgSimpleTimestamp('[', now.toOrgDate(), now.toOrgTime(), [], ']');
104+
final lastRepeat = OrgProperty(indent, ':LAST_REPEAT:',
105+
OrgContent([OrgPlainText(' '), timestamp]), '\n');
106+
107+
final note = OrgContent([
108+
// See org-log-note-headings
109+
OrgPlainText(
110+
'State ${'"$finalKeyword"'.padRight(12)} from ${'"$previousKeyword"'.padRight(12)}'),
111+
timestamp,
112+
OrgPlainText('\n'),
113+
]);
114+
105115
return copyWith(
106116
headline: headline,
107117
content: content,
108-
);
118+
).setProperty<OrgSection>(lastRepeat).addLogNote(note);
109119
}
110120

111121
OrgSection addLogNote(OrgContent note) {

lib/src/todo/model.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ class OrgTodoStates {
3939

4040
bool get isEmpty => todo.isEmpty && done.isEmpty;
4141
bool get isNotEmpty => !isEmpty;
42+
bool contains(String keyword) =>
43+
todo.contains(keyword) || done.contains(keyword);
44+
bool isEndState(String keyword) =>
45+
done.contains(keyword) || done.isEmpty && todo.last == keyword;
4246

4347
@override
4448
String toString() => 'TodoStates[${todo.join(' ')} | ${done.join(' ')}]';

test/org/model/section_test.dart

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,84 @@ void main() {
111111
- State "DONE" from "TODO" [2024-01-02 Tue 12:34]
112112
''');
113113
});
114+
group('custom states', () {
115+
group('manual example', () {
116+
final todoStates = [
117+
OrgTodoStates(
118+
todo: ['TODO', 'FEEDBACK', 'VERIFY'],
119+
done: ['DONE', 'CANCELED'])
120+
];
121+
final parser = OrgParserDefinition(todoStates: todoStates).build();
122+
test('next not done', () {
123+
final now = DateTime(2024, 2, 3, 23, 45);
124+
final result = parser.parse('''* TODO foo
125+
SCHEDULED: <2024-01-01 Mon +1w>
126+
:PROPERTIES:
127+
:LAST_REPEAT: [2024-01-02 Tue 12:34]
128+
:END:
129+
- State "DONE" from "TODO" [2024-01-02 Tue 12:34]
130+
''');
131+
final doc = result.value as OrgDocument;
132+
final section = doc.sections[0];
133+
final updated = section.cycleTodo(now: now, todoStates: todoStates);
134+
expect(updated.toMarkup(), '''* FEEDBACK foo
135+
SCHEDULED: <2024-01-08 Mon +1w>
136+
:PROPERTIES:
137+
:LAST_REPEAT: [2024-01-02 Tue 12:34]
138+
:END:
139+
- State "DONE" from "TODO" [2024-01-02 Tue 12:34]
140+
''');
141+
});
142+
test('next just one of done', () {
143+
final now = DateTime(2024, 2, 3, 23, 45);
144+
final result = parser.parse('''* VERIFY foo
145+
SCHEDULED: <2024-01-01 Mon +1w>
146+
:PROPERTIES:
147+
:LAST_REPEAT: [2024-01-02 Tue 12:34]
148+
:END:
149+
- State "DONE" from "TODO" [2024-01-02 Tue 12:34]
150+
''');
151+
final doc = result.value as OrgDocument;
152+
final section = doc.sections[0];
153+
final updated = section.cycleTodo(now: now, todoStates: todoStates);
154+
expect(updated.toMarkup(), '''* TODO foo
155+
SCHEDULED: <2024-01-08 Mon +1w>
156+
:PROPERTIES:
157+
:LAST_REPEAT: [2024-02-03 Sat 23:45]
158+
:END:
159+
- State "DONE" from "VERIFY" [2024-02-03 Sat 23:45]
160+
- State "DONE" from "TODO" [2024-01-02 Tue 12:34]
161+
''');
162+
});
163+
});
164+
group('no todo states', () {
165+
final todoStates = [
166+
OrgTodoStates(todo: [], done: ['TODO'])
167+
];
168+
final parser = OrgParserDefinition(todoStates: todoStates).build();
169+
test('from empty', () {
170+
final now = DateTime(2024, 2, 3, 23, 45);
171+
final result = parser.parse('''* foo
172+
SCHEDULED: <2024-01-01 Mon +1w>
173+
:PROPERTIES:
174+
:LAST_REPEAT: [2024-01-02 Tue 12:34]
175+
:END:
176+
- State "DONE" from "TODO" [2024-01-02 Tue 12:34]
177+
''');
178+
final doc = result.value as OrgDocument;
179+
final section = doc.sections[0];
180+
final updated = section.cycleTodo(now: now, todoStates: todoStates);
181+
expect(updated.toMarkup(), '''* foo
182+
SCHEDULED: <2024-01-08 Mon +1w>
183+
:PROPERTIES:
184+
:LAST_REPEAT: [2024-02-03 Sat 23:45]
185+
:END:
186+
- State "TODO" from "" [2024-02-03 Sat 23:45]
187+
- State "DONE" from "TODO" [2024-01-02 Tue 12:34]
188+
''');
189+
});
190+
});
191+
});
114192
});
115193
});
116194
group('set property', () {

0 commit comments

Comments
 (0)