tangled
alpha
login
or
join now
ngp.computer
/
tsk
A file-based task manager
0
fork
atom
overview
issues
pulls
pipelines
FIX: multiple styles on one line breaking rendering
ngp.computer
1 year ago
ce6d639b
95700381
+77
-52
4 changed files
expand all
collapse all
unified
split
Cargo.lock
Cargo.toml
src
main.rs
task.rs
+1
-1
Cargo.lock
···
582
583
[[package]]
584
name = "tsk"
585
-
version = "0.2.1"
586
dependencies = [
587
"clap",
588
"clap_complete",
···
582
583
[[package]]
584
name = "tsk"
585
+
version = "0.2.2"
586
dependencies = [
587
"clap",
588
"clap_complete",
+1
-1
Cargo.toml
···
1
[package]
2
name = "tsk"
3
-
version = "0.2.1"
4
edition = "2021"
5
publish = true
6
···
1
[package]
2
name = "tsk"
3
+
version = "0.2.2"
4
edition = "2021"
5
publish = true
6
+14
-7
src/main.rs
···
96
/// Shows raw file attributes for the file
97
#[arg(short = 'x', default_value_t = false)]
98
show_attrs: bool,
0
0
0
99
/// The [TSK-]ID of the task to display
100
#[command(flatten)]
101
task_id: TaskId,
···
232
Commands::Swap => command_swap(dir),
233
Commands::Show {
234
task_id,
0
235
show_attrs,
236
-
} => command_show(dir, task_id, show_attrs),
237
Commands::Follow {
238
task_id,
239
link_index,
···
385
Workspace::from_path(dir)?.deprioritize(task_id.into())
386
}
387
388
-
fn command_show(dir: PathBuf, task_id: TaskId, show_attrs: bool) -> Result<()> {
389
let task = Workspace::from_path(dir)?.task(task_id.into())?;
390
// YAML front-matter style. YAML is gross, but it's what everyone uses!
391
if show_attrs && !task.attributes.is_empty() {
···
395
}
396
println!("---");
397
}
398
-
if let Some(styled_task) = task::parse(&task.to_string()) {
399
-
writeln!(io::stdout(), "{}", styled_task.content)?;
400
-
} else {
401
-
println!("{task}");
0
0
0
402
}
403
Ok(())
404
}
···
421
if edit {
422
command_edit(dir, taskid)
423
} else {
424
-
command_show(dir, taskid, false)
425
}
426
}
427
}
···
96
/// Shows raw file attributes for the file
97
#[arg(short = 'x', default_value_t = false)]
98
show_attrs: bool,
99
+
100
+
#[arg(short = 'R', default_value_t = false)]
101
+
raw: bool,
102
/// The [TSK-]ID of the task to display
103
#[command(flatten)]
104
task_id: TaskId,
···
235
Commands::Swap => command_swap(dir),
236
Commands::Show {
237
task_id,
238
+
raw,
239
show_attrs,
240
+
} => command_show(dir, task_id, show_attrs, raw),
241
Commands::Follow {
242
task_id,
243
link_index,
···
389
Workspace::from_path(dir)?.deprioritize(task_id.into())
390
}
391
392
+
fn command_show(dir: PathBuf, task_id: TaskId, show_attrs: bool, raw: bool) -> Result<()> {
393
let task = Workspace::from_path(dir)?.task(task_id.into())?;
394
// YAML front-matter style. YAML is gross, but it's what everyone uses!
395
if show_attrs && !task.attributes.is_empty() {
···
399
}
400
println!("---");
401
}
402
+
match task::parse(&task.to_string()) {
403
+
Some(styled_task) if !raw => {
404
+
writeln!(io::stdout(), "{}", styled_task.content)?;
405
+
}
406
+
_ => {
407
+
println!("{task}");
408
+
}
409
}
410
Ok(())
411
}
···
428
if edit {
429
command_edit(dir, taskid)
430
} else {
431
+
command_show(dir, taskid, false, false)
432
}
433
}
434
}
+61
-43
src/task.rs
···
9
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
10
enum ParserState {
11
// Started by ` =`, terminated by `=
12
-
Highlight(usize),
13
// Started by ` [`, terminated by `](`
14
-
Linktext(usize),
15
// Started by `](`, terminated by `) `, must immedately follow a Linktext
16
-
Link(usize),
17
-
RawLink(usize),
18
// Started by ` [[`, terminated by `]] `
19
-
InternalLink(usize),
20
// Started by ` *`, terminated by `* `
21
-
Italics(usize),
22
// Started by ` !`, termianted by `!`
23
-
Bold(usize),
24
// Started by ` _`, terminated by `_ `
25
-
Underline(usize),
26
// Started by ` -`, terminated by `- `
27
-
Strikethrough(usize),
28
29
// TODO: implement these.
30
// Started by `_ `, terminated by `_`
···
37
// `\n` and followed by a `\n`
38
BlockEnd(usize),
39
// Started by ` ``, terminated by `` ` or `\n`
40
-
InlineBlock(usize),
41
// Started by `^\w+>`, terminated by `\n`
42
Blockquote(usize),
43
}
···
64
let state_last = state.last().cloned();
65
match stream.next() {
66
// there will always be an op code in the stack
67
-
Some((_, c)) => {
68
out.push(c);
69
let end = out.len() - 1;
70
match (last, c, state_last) {
71
('[', '[', _) => {
72
-
state.push(InternalLink(end));
73
}
74
-
(']', ']', Some(InternalLink(il))) => {
75
state.pop();
76
-
let contents = out.get(il + 1..out.len() - 2)?;
77
if let Ok(id) = Id::from_str(&contents) {
78
let linktext = format!(
79
"{}{}",
···
87
}
88
}
89
(' ' | '\r' | '\n', '[', _) => {
90
-
state.push(Linktext(end));
91
}
92
-
(']', '(', Some(Linktext(_))) => {
93
-
state.push(Link(end));
94
}
95
-
(')', ' ' | '\n' | '\r' | '.' | '!' | '?', Some(Link(_))) => {
96
-
let linkpos = if let Link(lp) = state.pop().unwrap() {
0
0
97
lp
98
} else {
99
// remove the linktext state, it is always present.
100
state.pop();
101
continue;
102
};
103
-
let linktextpos = if let Linktext(lt) = state.pop().unwrap() {
104
lt
105
} else {
106
continue;
···
116
out.replace_range(linktextpos..end, &linktext);
117
}
118
}
119
-
('>', ' ' | '\n' | '\r' | '.' | '!' | '?', Some(RawLink(hl))) => {
0
0
120
state.pop();
121
-
let link = out.get(hl + 1..out.len() - 2)?;
122
if let Ok(url) = Url::parse(link) {
123
let linktext =
124
format!("{}{}", link.blue(), super_num(links.len() + 1).purple());
···
127
}
128
}
129
(' ' | '\r' | '\n', '<', _) => {
130
-
state.push(RawLink(end));
131
}
132
-
('=', ' ' | '\n' | '\r' | '.' | '!' | '?', Some(Highlight(hl))) => {
0
0
133
state.pop();
134
out.replace_range(
135
hl..end,
136
-
&out.get(hl + 1..out.len() - 2)?.reversed().to_string(),
137
);
138
}
139
(' ' | '\r' | '\n', '=', _) => {
140
-
state.push(Highlight(end));
141
}
142
(' ' | '\r' | '\n', '*', _) => {
143
-
state.push(Italics(end));
144
}
145
-
('*', ' ' | '\n' | '\r' | '.' | '!' | '?', Some(Italics(il))) => {
0
0
146
state.pop();
147
out.replace_range(
148
il..end,
149
-
&out.get(il + 1..out.len() - 2)?.italic().to_string(),
150
);
151
}
152
(' ' | '\r' | '\n', '!', _) => {
153
-
state.push(Bold(end));
154
}
155
-
('!', ' ' | '\n' | '\r' | '.' | '!' | '?', Some(Bold(il))) => {
0
0
156
state.pop();
157
-
out.replace_range(il..end, &out.get(il + 1..end - 1)?.bold().to_string());
0
0
0
158
}
159
(' ' | '\r' | '\n', '_', _) => {
160
-
state.push(Underline(end));
161
}
162
-
('_', ' ' | '\n' | '\r' | '.' | '!' | '?', Some(Underline(il))) => {
0
0
163
state.pop();
164
out.replace_range(
165
il..end,
166
-
&out.get(il + 1..end - 1)?.underline().to_string(),
167
);
168
}
169
(' ' | '\r' | '\n', '~', _) => {
170
-
state.push(Strikethrough(end));
171
}
172
-
('~', ' ' | '\n' | '\r' | '.' | '!' | '?', Some(Strikethrough(il))) => {
0
0
173
state.pop();
174
out.replace_range(
175
il..end,
176
-
&out.get(il + 1..end - 1)?.strikethrough().to_string(),
177
);
178
}
179
-
('`', ' ' | '\n' | '\r' | '.' | '!' | '?', Some(InlineBlock(hl))) => {
180
-
state.pop();
0
181
out.replace_range(
182
hl..end,
183
-
&out.get(hl + 1..out.len() - 1)?.green().to_string(),
184
);
185
}
186
-
(' ' | '\r' | '\n', '`', _) => {
187
-
state.push(InlineBlock(end));
188
}
189
_ => (),
190
}
···
9
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
10
enum ParserState {
11
// Started by ` =`, terminated by `=
12
+
Highlight(usize, usize),
13
// Started by ` [`, terminated by `](`
14
+
Linktext(usize, usize),
15
// Started by `](`, terminated by `) `, must immedately follow a Linktext
16
+
Link(usize, usize),
17
+
RawLink(usize, usize),
18
// Started by ` [[`, terminated by `]] `
19
+
InternalLink(usize, usize),
20
// Started by ` *`, terminated by `* `
21
+
Italics(usize, usize),
22
// Started by ` !`, termianted by `!`
23
+
Bold(usize, usize),
24
// Started by ` _`, terminated by `_ `
25
+
Underline(usize, usize),
26
// Started by ` -`, terminated by `- `
27
+
Strikethrough(usize, usize),
28
29
// TODO: implement these.
30
// Started by `_ `, terminated by `_`
···
37
// `\n` and followed by a `\n`
38
BlockEnd(usize),
39
// Started by ` ``, terminated by `` ` or `\n`
40
+
InlineBlock(usize, usize),
41
// Started by `^\w+>`, terminated by `\n`
42
Blockquote(usize),
43
}
···
64
let state_last = state.last().cloned();
65
match stream.next() {
66
// there will always be an op code in the stack
67
+
Some((char_pos, c)) => {
68
out.push(c);
69
let end = out.len() - 1;
70
match (last, c, state_last) {
71
('[', '[', _) => {
72
+
state.push(InternalLink(end, char_pos));
73
}
74
+
(']', ']', Some(InternalLink(il, s_pos))) => {
75
state.pop();
76
+
let contents = s.get(s_pos + 1..char_pos - 1)?;
77
if let Ok(id) = Id::from_str(&contents) {
78
let linktext = format!(
79
"{}{}",
···
87
}
88
}
89
(' ' | '\r' | '\n', '[', _) => {
90
+
state.push(Linktext(end, char_pos));
91
}
92
+
(']', '(', Some(Linktext(_, _))) => {
93
+
state.push(Link(end, char_pos));
94
}
95
+
(')', ' ' | '\n' | '\r' | '.' | '!' | '?', Some(Link(_, _))) => {
96
+
// TODO: this needs to be updated to use `s` instead of `out` for position
97
+
// parsing
98
+
let linkpos = if let Link(lp, _) = state.pop().unwrap() {
99
lp
100
} else {
101
// remove the linktext state, it is always present.
102
state.pop();
103
continue;
104
};
105
+
let linktextpos = if let Linktext(lt, _) = state.pop().unwrap() {
106
lt
107
} else {
108
continue;
···
118
out.replace_range(linktextpos..end, &linktext);
119
}
120
}
121
+
('>', ' ' | '\n' | '\r' | '.' | '!' | '?', Some(RawLink(hl, s_pos)))
122
+
if s_pos != char_pos - 1 =>
123
+
{
124
state.pop();
125
+
let link = s.get(s_pos + 1..char_pos - 1)?;
126
if let Ok(url) = Url::parse(link) {
127
let linktext =
128
format!("{}{}", link.blue(), super_num(links.len() + 1).purple());
···
131
}
132
}
133
(' ' | '\r' | '\n', '<', _) => {
134
+
state.push(RawLink(end, char_pos));
135
}
136
+
('=', ' ' | '\n' | '\r' | '.' | '!' | '?', Some(Highlight(hl, s_pos)))
137
+
if s_pos != char_pos - 1 =>
138
+
{
139
state.pop();
140
out.replace_range(
141
hl..end,
142
+
&s.get(s_pos + 1..char_pos - 1)?.reversed().to_string(),
143
);
144
}
145
(' ' | '\r' | '\n', '=', _) => {
146
+
state.push(Highlight(end, char_pos));
147
}
148
(' ' | '\r' | '\n', '*', _) => {
149
+
state.push(Italics(end, char_pos));
150
}
151
+
('*', ' ' | '\n' | '\r' | '.' | '!' | '?', Some(Italics(il, s_pos)))
152
+
if s_pos != char_pos - 1 =>
153
+
{
154
state.pop();
155
out.replace_range(
156
il..end,
157
+
&s.get(s_pos + 1..char_pos - 1)?.italic().to_string(),
158
);
159
}
160
(' ' | '\r' | '\n', '!', _) => {
161
+
state.push(Bold(end, char_pos));
162
}
163
+
('!', ' ' | '\n' | '\r' | '.' | '!' | '?', Some(Bold(il, s_pos)))
164
+
if s_pos != char_pos - 1 =>
165
+
{
166
state.pop();
167
+
out.replace_range(
168
+
il..end,
169
+
&s.get(s_pos + 1..char_pos - 1)?.bold().to_string(),
170
+
);
171
}
172
(' ' | '\r' | '\n', '_', _) => {
173
+
state.push(Underline(end, char_pos));
174
}
175
+
('_', ' ' | '\n' | '\r' | '.' | '!' | '?', Some(Underline(il, s_pos)))
176
+
if s_pos != char_pos - 1 =>
177
+
{
178
state.pop();
179
out.replace_range(
180
il..end,
181
+
&s.get(s_pos + 1..char_pos - 1)?.underline().to_string(),
182
);
183
}
184
(' ' | '\r' | '\n', '~', _) => {
185
+
state.push(Strikethrough(end, char_pos));
186
}
187
+
('~', ' ' | '\n' | '\r' | '.' | '!' | '?', Some(Strikethrough(il, s_pos)))
188
+
if s_pos != char_pos - 1 =>
189
+
{
190
state.pop();
191
out.replace_range(
192
il..end,
193
+
&s.get(s_pos + 1..char_pos - 1)?.strikethrough().to_string(),
194
);
195
}
196
+
('`', ' ' | '\n' | '\r' | '.' | '!' | '?', Some(InlineBlock(hl, s_pos)))
197
+
if s_pos != char_pos - 1 =>
198
+
{
199
out.replace_range(
200
hl..end,
201
+
&s.get(s_pos + 1..char_pos - 1)?.green().to_string(),
202
);
203
}
204
+
(' ' | '\n' | '\r' | '.' | '!' | '?', '`', _) => {
205
+
state.push(InlineBlock(end, char_pos));
206
}
207
_ => (),
208
}