Skip to content

Commit b5197e8

Browse files
committed
fix more edge cases
1 parent c6da950 commit b5197e8

File tree

2 files changed

+96
-25
lines changed

2 files changed

+96
-25
lines changed

web_src/js/features/comp/EditorMarkdown.test.ts

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,20 @@ test('textareaSplitLines', () => {
2424
});
2525

2626
test('markdownHandleListNumbers', () => {
27-
const testInput = (input: string, expected: string) => {
27+
const testInput = (input: string, expected?: string) => {
2828
const inputPos = input.indexOf('|');
2929
input = input.replace('|', '');
3030
const ret = markdownHandleListNumbers({value: input, selStart: inputPos, selEnd: inputPos});
31-
32-
const expectedPos = expected.indexOf('|');
33-
expected = expected.replace('|', '');
34-
expect(ret).toEqual({handled: true, valueSelection: {value: expected, selStart: expectedPos, selEnd: expectedPos}});
31+
if (expected === null) {
32+
expect(ret).toEqual({handled: false});
33+
} else {
34+
const expectedPos = expected.indexOf('|');
35+
expected = expected.replace('|', '');
36+
expect(ret).toEqual({
37+
handled: true,
38+
valueSelection: {value: expected, selStart: expectedPos, selEnd: expectedPos},
39+
});
40+
}
3541
};
3642

3743
testInput(`
@@ -42,13 +48,47 @@ test('markdownHandleListNumbers', () => {
4248
|
4349
`);
4450

51+
testInput(`
52+
|1. a
53+
`, null); // let browser handle it
54+
4555
testInput(`
4656
1. a
4757
1. b|
4858
`, `
4959
1. a
5060
2. b
5161
3. |
62+
`);
63+
64+
testInput(`
65+
2. a
66+
2. b|
67+
68+
1. x
69+
1. y
70+
`, `
71+
1. a
72+
2. b
73+
3. |
74+
75+
1. x
76+
1. y
77+
`);
78+
79+
testInput(`
80+
2. a
81+
2. b
82+
83+
1. x|
84+
1. y
85+
`, `
86+
2. a
87+
2. b
88+
89+
1. x
90+
2. |
91+
3. y
5292
`);
5393

5494
testInput(`
@@ -60,6 +100,35 @@ test('markdownHandleListNumbers', () => {
60100
2. b
61101
3. |
62102
4. c
103+
`);
104+
105+
testInput(`
106+
1. a
107+
1. b
108+
2. b
109+
3. b
110+
4. b
111+
1. c|
112+
`, `
113+
1. a
114+
1. b
115+
2. b
116+
3. b
117+
4. b
118+
2. c
119+
3. |
120+
`);
121+
122+
// this is a special case, it's difficult to re-format the parent level at the moment, so leave it to the future
123+
testInput(`
124+
1. a
125+
2. b|
126+
3. c
127+
`, `
128+
1. a
129+
1. b
130+
2. |
131+
3. c
63132
`);
64133
});
65134

web_src/js/features/comp/EditorMarkdown.ts

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,23 @@ export function textareaSplitLines(value: string, pos: number): TextLinesBuffer
8989
}
9090

9191
function markdownReformatListNumbers(linesBuf: TextLinesBuffer, indention: string) {
92-
const re = new RegExp(`^${indention}([0-9]+)\\.`);
93-
let firstLineIdx = 0;
92+
const reDeeperIndention = new RegExp(`^${indention}\\s+`);
93+
const reSameLevel = new RegExp(`^${indention}([0-9]+)\\.`);
94+
let firstLineIdx: number;
9495
for (firstLineIdx = linesBuf.posLineIndex - 1; firstLineIdx >= 0; firstLineIdx--) {
95-
if (!re.test(linesBuf.lines[firstLineIdx])) break;
96+
const line = linesBuf.lines[firstLineIdx];
97+
if (!reDeeperIndention.test(line) && !reSameLevel.test(line)) break;
9698
}
9799
firstLineIdx++;
100+
let num = 1;
98101
for (let i = firstLineIdx; i < linesBuf.lines.length; i++) {
99-
if (!re.test(linesBuf.lines[i])) break;
100-
linesBuf.lines[i] = `${indention}${i - firstLineIdx + 1}.${linesBuf.lines[i].replace(re, '')}`;
102+
const line = linesBuf.lines[i];
103+
const sameLevel = reSameLevel.test(line);
104+
if (!sameLevel && !reDeeperIndention.test(line)) break;
105+
if (sameLevel) {
106+
linesBuf.lines[i] = `${indention}${num}.${line.replace(reSameLevel, '')}`;
107+
num++;
108+
}
101109
}
102110
linesBuf.lengthBeforePosLine = 0;
103111
for (let i = 0; i < linesBuf.posLineIndex; i++) {
@@ -107,36 +115,30 @@ function markdownReformatListNumbers(linesBuf: TextLinesBuffer, indention: strin
107115
}
108116

109117
export function markdownHandleListNumbers(tvs: TextareaValueSelection): MarkdownHandleListNumbersResult {
110-
const ret: MarkdownHandleListNumbersResult = {handled: false};
111-
if (tvs.selEnd !== tvs.selStart) return ret; // do not process when there is a selection
112-
113-
const value = tvs.value;
114-
// find the current line
115-
// * if selStart is 0, lastIndexOf(..., -1) is the same as lastIndexOf(..., 0)
116-
// * if lastIndexOf reruns -1, lineStart is 0 and it is still correct.
117-
const lineStart = value.lastIndexOf('\n', tvs.selStart - 1) + 1;
118-
let lineEnd = value.indexOf('\n', tvs.selStart);
119-
lineEnd = lineEnd < 0 ? value.length : lineEnd;
120-
let line = value.slice(lineStart, lineEnd);
121-
if (!line) return; // if the line is empty, do nothing, let the browser handle it
118+
const unhandled: MarkdownHandleListNumbersResult = {handled: false};
119+
if (tvs.selEnd !== tvs.selStart) return unhandled; // do not process when there is a selection
120+
121+
const linesBuf = textareaSplitLines(tvs.value, tvs.selStart);
122+
let line = linesBuf.lines[linesBuf.posLineIndex] ?? '';
123+
if (!line) return unhandled; // if the line is empty, do nothing, let the browser handle it
122124

123125
// parse the indention
124126
const indention = /^\s*/.exec(line)[0];
125127
line = line.slice(indention.length);
128+
if (linesBuf.inlinePos <= indention.length) return unhandled; // if cursor is at the indention, do nothing, let the browser handle it
126129

127130
// parse the prefixes: "1. ", "- ", "* ", there could also be " [ ] " or " [x] " for task lists
128131
// there must be a space after the prefix because none of "1.foo" / "-foo" is a list item
129132
const prefixMatch = /^([0-9]+\.|[-*])(\s\[([ x])\])?\s/.exec(line);
130133
let prefix = '';
131134
if (prefixMatch) {
132135
prefix = prefixMatch[0];
133-
if (lineStart + prefix.length > tvs.selStart) prefix = ''; // do not add new line if cursor is at prefix
136+
if (prefix.length > linesBuf.inlinePos) prefix = ''; // do not add new line if cursor is at prefix
134137
}
135138

136139
line = line.slice(prefix.length);
137-
if (!indention && !prefix) return ret; // if no indention and no prefix, do nothing, let the browser handle it
140+
if (!indention && !prefix) return unhandled; // if no indention and no prefix, do nothing, let the browser handle it
138141

139-
const linesBuf = textareaSplitLines(value, tvs.selStart);
140142
if (!line) {
141143
// clear current line if we only have i.e. '1. ' and the user presses enter again to finish creating a list
142144
linesBuf.lines[linesBuf.posLineIndex] = '';

0 commit comments

Comments
 (0)