Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add keyboard shortcuts to toggle between list and task list formats #1238

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ Tip: also support the option `completion.root`
| <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>]</kbd> | Toggle heading (uplevel) |
| <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>[</kbd> | Toggle heading (downlevel) |
| <kbd>Ctrl</kbd>/<kbd>Cmd</kbd> + <kbd>M</kbd> | Toggle math environment |
| <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd>C</kbd> | Toggle list <--> task list |
| <kbd>Alt</kbd> + <kbd>C</kbd> | Check/Uncheck task list item |
| <kbd>Ctrl</kbd>/<kbd>Cmd</kbd> + <kbd>Shift</kbd> + <kbd>V</kbd> | Toggle preview |
| <kbd>Ctrl</kbd>/<kbd>Cmd</kbd> + <kbd>K</kbd> <kbd>V</kbd> | Toggle preview to side |
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@
"mac": "cmd+[",
"when": "editorTextFocus && editorLangId =~ /^markdown$|^rmd$|^quarto$/ && !suggestWidgetVisible"
},
{
"command": "markdown.extension.toggleTaskList",
"key": "shift+alt+c",
"when": "editorTextFocus && editorLangId =~ /^markdown$|^rmd$|^quarto$/"
},
{
"command": "markdown.extension.checkTaskList",
"key": "alt+c",
Expand Down
52 changes: 52 additions & 0 deletions src/listEditing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export function activate(context: ExtensionContext) {
commands.registerCommand('markdown.extension.onTabKey', onTabKey),
commands.registerCommand('markdown.extension.onShiftTabKey', () => { return onTabKey('shift'); }),
commands.registerCommand('markdown.extension.onBackspaceKey', onBackspaceKey),
commands.registerCommand('markdown.extension.toggleTaskList', toggleTaskList),
commands.registerCommand('markdown.extension.checkTaskList', checkTaskList),
commands.registerCommand('markdown.extension.onMoveLineDown', onMoveLineDown),
commands.registerCommand('markdown.extension.onMoveLineUp', onMoveLineUp),
Expand Down Expand Up @@ -446,6 +447,57 @@ function deleteRange(editor: TextEditor, range: Range): Thenable<boolean> {
);
}

function toggleTaskList(): Thenable<unknown> | void {
// - Look into lines that be lists/task-lists.
// - The first matching line dictates the new state for all further lines.
// - I.e. if the first line is task-lists, only other task-lists lines will
// be considered, and vice versa.
const editor = window.activeTextEditor!;
const listRegex = /^(\s*)([-+*]|[0-9]+[.)])(?! +\[[x ]\]) +/
const tasklistRegex = /^(\s*)([-+*]|[0-9]+[.)]) +\[[x ]\] */
let replaceRanges: Range[] = []
let newState: boolean | undefined = undefined // true = "- [ ]", false = "- ", undefined = no matching lines

// go through all touched lines of all selections.
for (const selection of editor.selections) {
for (let i = selection.start.line; i <= selection.end.line; i++) {
const line = editor.document.lineAt(i);
const lineStart = line.range.start;

if (!selection.isSingleLine && (selection.start.isEqual(line.range.end) || selection.end.isEqual(line.range.start))) {
continue;
}

let matches: RegExpExecArray | null;
if (
(matches = listRegex.exec(line.text))
) {
if(newState === undefined)newState = true;
} else if (
(matches = tasklistRegex.exec(line.text))
) {
if(newState === undefined)newState = false;
}

if(matches){
replaceRanges.push(new Range(
lineStart.with({ character: matches[1].length }),
lineStart.with({ character: matches[0].length}),
));
}
}
}

if (newState !== undefined) {
const newChar = newState ? '- [ ] ' : '- ';
return editor.edit(editBuilder => {
for (const range of replaceRanges) {
editBuilder.replace(range, newChar);
}
});
}
}

function checkTaskList(): Thenable<unknown> | void {
// - Look into selections for lines that could be checked/unchecked.
// - The first matching line dictates the new state for all further lines.
Expand Down
60 changes: 60 additions & 0 deletions src/test/suite/integration/listEditing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,66 @@ suite("List editing.", () => {
new Selection(0, 10, 0, 10));
});

test("List/TaskList toggle. 1: Check single line,list to task list", () => {
return testCommand('markdown.extension.toggleTaskList',
[
'- test'
],
new Selection(0, 0, 0, 0),
[
'- [ ] test'
],
new Selection(0, 0, 0, 0),
);
});

test("List/TaskList toggle. 2: Check single line,task list to list", () => {
return testCommand('markdown.extension.toggleTaskList',
[
'- [ ] test'
],
new Selection(0, 0, 0, 0),
[
'- test'
],
new Selection(0, 0, 0, 0),
);
});

test("List/TaskList toggle. 3: Check multiple lines,list to task list", () => {
return testCommand('markdown.extension.toggleTaskList',
[
'- test',
' - test',
'- test',
],
new Selection(1, 0, 2, 1),
[
'- test',
' - [ ] test',
'- [ ] test',
],
new Selection(1, 0, 2, 1),
);
});

test("List/TaskList toggle. 4: Check multiple lines,task list to list", () => {
return testCommand('markdown.extension.toggleTaskList',
[
' - [x] test',
' - test',
'- [ ] test',
],
new Selection(0, 0, 2, 1),
[
' - test',
' - test',
'- test',
],
new Selection(0, 0, 2, 1),
);
});

test("List toggle. 1: Check single line", () => {
return testCommand('markdown.extension.checkTaskList',
[
Expand Down