From 2bd934a69902d46266d6fa1c85d995dbe47027b8 Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Tue, 9 Oct 2018 10:56:42 -0500 Subject: [PATCH 01/27] Brought in checkboxes extension as a feature with initial test structure. --- src/checkboxes.ts | 174 +++++++++++++++++++++++++++++++++++ test/checkboxes.test.ts | 27 ++++++ test/fixtures/checkboxes.org | 21 +++++ 3 files changed, 222 insertions(+) create mode 100644 src/checkboxes.ts create mode 100644 test/checkboxes.test.ts create mode 100644 test/fixtures/checkboxes.org diff --git a/src/checkboxes.ts b/src/checkboxes.ts new file mode 100644 index 0000000..1b751a0 --- /dev/null +++ b/src/checkboxes.ts @@ -0,0 +1,174 @@ +'use strict'; + +import { window, TextLine, Range, TextEditor, TextEditorEdit } from 'vscode'; + +// Checkbox is represented by exactly one symbol between square brackets. Symbol indicates status: '-' undetermined, 'x' or 'X' checked, ' ' unchecked. +const checkboxRegex = /\[([-xX ])\]/; +// Summary is a cookie indicating the number of ticked checkboxes in the child list relative to the total number of checkboxes in the list. +const summaryRegex = /\[(\d*\/\d*)\]/; +// Percentage is a cookie indicating the percentage of ticked checkboxes in the child list relative to the total number of checkboxes in the list. +const percentRegex = /\[(\d*)%\]/; +const indentRegex = /^(\s*)\S/; + +export function OrgToggleCheckbox(editor: TextEditor, edit: TextEditorEdit) { + let doc = editor.document; + let line = doc.lineAt(editor.selection.active.line); + let checkbox = this.orgFindCookie(checkboxRegex, line); + if (checkbox) { + let text = doc.getText(checkbox); + var delta = orgCascadeCheckbox(edit, checkbox, line, text == ' ' || text == '-'); + let parent = orgFindParent(editor, line); + // Since the updates as a result of toggle have not happened yet in the editor, counting checked children is going to use old value of current checkbox. Hence the adjustment. + orgUpdateParent(editor, edit, parent, delta); + } +} + +export function OrgUpdateSummary(editor: TextEditor, edit: TextEditorEdit) { + let doc = editor.document; + let line = doc.lineAt(editor.selection.active.line); + orgUpdateParent(editor, edit, line, 0); +} + +function orgFindCookie(cookie: RegExp, line: TextLine): Range { + let match = cookie.exec(line.text); + if (match) { + return new Range(line.lineNumber, match.index + 1, line.lineNumber, match.index + 2); + } + return null; +} + +// Calculate and return indentation level of the line. Used in traversing nested lists and locating parent item. +function orgGetIndent(line: TextLine): number { + let match = indentRegex.exec(line.text); + if (match) { + // TODO: Convert tabs to spaces? + return match[1].length; + } + return 0; +} + +// Perform the toggle. 'x' or 'X' becomes blank and blank becomes 'X'. +function orgCascadeCheckbox(edit: TextEditorEdit, checkbox: Range, line: TextLine, toCheck: boolean): number { + if (!checkbox) { + return 0; + } + let editor = window.activeTextEditor; + let text = editor.document.getText(checkbox); + let isChecked = text != ' ' && text != '-'; + if (isChecked == toCheck) { + return 0; // Nothing to do. + } + edit.replace(checkbox, toCheck ? 'X' : ' '); + if (!line) { + return toCheck ? 1 : -1; + } + let children = orgFindChildren(editor, line); + let child: TextLine = null; + for (child of children) { + orgCascadeCheckbox(edit, orgFindCookie(checkboxRegex, child), child, toCheck); + } + // If there is a summary on this line, update it to either [0/0] or [total/total] depending on value of 'check'. + let total = toCheck ? children.length : 0; + let summary = orgFindCookie(summaryRegex, line); + if (summary) { + edit.replace(summary, total.toString() + '/' + total.toString()); + } + let percent = orgFindCookie(percentRegex, line); + if (percent) { + total = toCheck ? 100 : 0; + edit.replace(summary, total.toString()); + } + return toCheck ? 1 : -1; +} + +// Find parent item by walking lines up to the start of the file looking for a smaller indentation. Does not ignore blank lines (indentation 0). +function orgFindParent(editor: TextEditor, line: TextLine): TextLine { + let lnum = line.lineNumber; + let indent = orgGetIndent(line); + let parent = null; + let pindent = indent; + let doc = editor.document; + while (pindent >= indent) { + lnum--; + if (lnum < 0) { + return null; + } + + parent = doc.lineAt(lnum); + pindent = orgGetIndent(parent); + } + return parent; +} + +// Update checkbox and summary on this line. Adjust checked items count with an additional offset. That accounts for +// a checkbox that has just been toggled but text in the editor has not been updated yet. +function orgUpdateParent(editor: TextEditor, edit: TextEditorEdit, line: TextLine, adjust: number) { + if (!line) { + return; + } + let children = orgFindChildren(editor, line); + let total = children.length; + if (total == 0) { + return; + } + let checked = adjust; + let chk = null; + let doc = editor.document; + for (let child of children) { + chk = orgFindCookie(checkboxRegex, child); + if (chk) { + if (doc.getText(chk) != ' ') { + checked++; + } + } + } + let summary = orgFindCookie(summaryRegex, line); + if (summary) { + edit.replace(summary, checked.toString() + '/' + total.toString()); + } + let percent = orgFindCookie(percentRegex, line); + if (percent) { + edit.replace(percent, total == 0 ? '0' : (checked * 100 / total).toString()); + } + // If there is a checkbox on this line, update it depending on (checked == total). + chk = orgFindCookie(checkboxRegex, line); + // Prevent propagation downstream by passing line = null. + let delta = orgCascadeCheckbox(edit, chk, null, checked == total); + // Recursively update parent nodes + let parent = orgFindParent(editor, line); + // Since the updates as a result of toggle have not happened yet in the editor, counting checked children is going to use old value of current checkbox. Hence the adjustment. + orgUpdateParent(editor, edit, parent, delta); +} + +// Find parent item by walking lines up to the start of the file looking for a smaller indentation. Does not ignore blank lines (indentation 0). +function orgFindChildren(editor: TextEditor, line: TextLine): TextLine[] { + let children: TextLine[] = []; + let lnum = line.lineNumber; + let doc = editor.document; + let lmax = doc.lineCount - 1; + let indent = orgGetIndent(line); + let child: TextLine = null; + let cindent = indent; + let next_indent = -1; + while (lnum < lmax) { + lnum++; + child = doc.lineAt(lnum); + cindent = orgGetIndent(child); + if (cindent <= indent) { + break; + } + if (next_indent < 0) { + next_indent = cindent; + } + // TODO: Handle weird indentation like this: + // current + // child 1 + // child 2 + // child 3 + // Are all the above children considered siblings? + if (cindent <= next_indent) { + children.push(child); + } + } + return children; +} diff --git a/test/checkboxes.test.ts b/test/checkboxes.test.ts new file mode 100644 index 0000000..1211bbf --- /dev/null +++ b/test/checkboxes.test.ts @@ -0,0 +1,27 @@ +'use strict'; + +import 'mocha'; +import * as assert from 'assert'; +import { Position, Selection, TextDocument, window, workspace } from 'vscode'; +import { join } from 'path'; +import * as checkboxes from '../src/checkboxes'; + +suite('checkboxes', () => { + test('Can update summary', () => { + const filePath = join(__dirname, '../../test/fixtures/checkboxes.org'); + let textDocument: TextDocument; + workspace.openTextDocument(filePath).then(document => { + textDocument = document; + return window.showTextDocument(document); + }).then(editor => { + const pos = new Position(10, 5); + editor.selection = new Selection(pos, pos); + return editor.edit(edit => { + checkboxes.OrgUpdateSummary(editor, edit); + }); + }).then(() => { + var line = textDocument.lineAt(10).text; + assert.equal(line, '* TODO Implement tests [0%] [0/4]'); + }); + }); +}); diff --git a/test/fixtures/checkboxes.org b/test/fixtures/checkboxes.org new file mode 100644 index 0000000..e122e2b --- /dev/null +++ b/test/fixtures/checkboxes.org @@ -0,0 +1,21 @@ +* TODO Organize party [/] + - [-] call people [/] + - [ ] Peter + - [X] Sarah + - [ ] Sam + - [X] order food + - [ ] think about what music to play + - [X] talk to the neighbors + +* TODO Implement tests [%] [/] + - [ ] updates summary cookie + - [ ] updates percent cookie + - [ ] toggling child checkbox [%] + - [ ] updates parent summary/percent cookie + - [ ] sets parent to on if all children are on + - [ ] sets parent to off when all children are off + - [ ] sets parent to undetermined when some children are on and some are off + - [ ] toggling parent checkbox [/] + - [ ] updates summary/percent cookie + - [ ] sets all children to on when parent is on + - [ ] sets all children to off when parent is off From 0d76b67a5473691ef953f7a69b23c92c88ee7e6a Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Tue, 9 Oct 2018 13:15:41 -0500 Subject: [PATCH 02/27] Make summary update test pass. --- src/checkboxes.ts | 33 +++++++++++++-------------------- test/checkboxes.test.ts | 13 +++++++------ 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/checkboxes.ts b/src/checkboxes.ts index 1b751a0..3862a06 100644 --- a/src/checkboxes.ts +++ b/src/checkboxes.ts @@ -13,10 +13,10 @@ const indentRegex = /^(\s*)\S/; export function OrgToggleCheckbox(editor: TextEditor, edit: TextEditorEdit) { let doc = editor.document; let line = doc.lineAt(editor.selection.active.line); - let checkbox = this.orgFindCookie(checkboxRegex, line); + let checkbox = orgFindCookie(checkboxRegex, line); if (checkbox) { let text = doc.getText(checkbox); - var delta = orgCascadeCheckbox(edit, checkbox, line, text == ' ' || text == '-'); + var delta = orgCascadeCheckbox(edit, checkbox, line, !orgIsChecked(text)); let parent = orgFindParent(editor, line); // Since the updates as a result of toggle have not happened yet in the editor, counting checked children is going to use old value of current checkbox. Hence the adjustment. orgUpdateParent(editor, edit, parent, delta); @@ -29,12 +29,16 @@ export function OrgUpdateSummary(editor: TextEditor, edit: TextEditorEdit) { orgUpdateParent(editor, edit, line, 0); } -function orgFindCookie(cookie: RegExp, line: TextLine): Range { +function orgIsChecked(value: string): boolean { + return value == 'x' || value == 'X'; +} + +function orgFindCookie(cookie: RegExp, line: TextLine): Range | undefined { let match = cookie.exec(line.text); if (match) { - return new Range(line.lineNumber, match.index + 1, line.lineNumber, match.index + 2); + return new Range(line.lineNumber, match.index + 1, line.lineNumber, match.index + 1 + match[1].length); } - return null; + return undefined; } // Calculate and return indentation level of the line. Used in traversing nested lists and locating parent item. @@ -54,7 +58,7 @@ function orgCascadeCheckbox(edit: TextEditorEdit, checkbox: Range, line: TextLin } let editor = window.activeTextEditor; let text = editor.document.getText(checkbox); - let isChecked = text != ' ' && text != '-'; + let isChecked = orgIsChecked(text); if (isChecked == toCheck) { return 0; // Nothing to do. } @@ -76,7 +80,7 @@ function orgCascadeCheckbox(edit: TextEditorEdit, checkbox: Range, line: TextLin let percent = orgFindCookie(percentRegex, line); if (percent) { total = toCheck ? 100 : 0; - edit.replace(summary, total.toString()); + edit.replace(percent, total.toString()); } return toCheck ? 1 : -1; } @@ -108,18 +112,13 @@ function orgUpdateParent(editor: TextEditor, edit: TextEditorEdit, line: TextLin } let children = orgFindChildren(editor, line); let total = children.length; - if (total == 0) { - return; - } let checked = adjust; let chk = null; let doc = editor.document; for (let child of children) { chk = orgFindCookie(checkboxRegex, child); - if (chk) { - if (doc.getText(chk) != ' ') { - checked++; - } + if (orgIsChecked(doc.getText(chk))) { + checked++; } } let summary = orgFindCookie(summaryRegex, line); @@ -160,12 +159,6 @@ function orgFindChildren(editor: TextEditor, line: TextLine): TextLine[] { if (next_indent < 0) { next_indent = cindent; } - // TODO: Handle weird indentation like this: - // current - // child 1 - // child 2 - // child 3 - // Are all the above children considered siblings? if (cindent <= next_indent) { children.push(child); } diff --git a/test/checkboxes.test.ts b/test/checkboxes.test.ts index 1211bbf..3e57799 100644 --- a/test/checkboxes.test.ts +++ b/test/checkboxes.test.ts @@ -6,22 +6,23 @@ import { Position, Selection, TextDocument, window, workspace } from 'vscode'; import { join } from 'path'; import * as checkboxes from '../src/checkboxes'; -suite('checkboxes', () => { - test('Can update summary', () => { +suite('Checkboxes', () => { + test('Can update summary', done => { const filePath = join(__dirname, '../../test/fixtures/checkboxes.org'); + let expected = '* TODO Implement tests [0%] [0/4]'; let textDocument: TextDocument; workspace.openTextDocument(filePath).then(document => { textDocument = document; return window.showTextDocument(document); }).then(editor => { - const pos = new Position(10, 5); + const pos = new Position(9, 5); editor.selection = new Selection(pos, pos); return editor.edit(edit => { checkboxes.OrgUpdateSummary(editor, edit); }); }).then(() => { - var line = textDocument.lineAt(10).text; - assert.equal(line, '* TODO Implement tests [0%] [0/4]'); - }); + var actual = textDocument.lineAt(9).text; + assert.equal(actual, expected); + }).then(done, done); }); }); From 4618130fe987e46a94f2a56794e06ec3ac7c0b31 Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Tue, 9 Oct 2018 13:16:37 -0500 Subject: [PATCH 03/27] Added checkbox toggle test. --- test/checkboxes.test.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/checkboxes.test.ts b/test/checkboxes.test.ts index 3e57799..d4d61a0 100644 --- a/test/checkboxes.test.ts +++ b/test/checkboxes.test.ts @@ -25,4 +25,22 @@ suite('Checkboxes', () => { assert.equal(actual, expected); }).then(done, done); }); + test('Ticking checkbox updates parent', done => { + const filePath = join(__dirname, '../../test/fixtures/checkboxes.org'); + let expected = ' - [-] toggling child checkbox [25%]'; + let textDocument: TextDocument; + workspace.openTextDocument(filePath).then(document => { + textDocument = document; + return window.showTextDocument(document); + }).then(editor => { + const pos = new Position(14, 14); + editor.selection = new Selection(pos, pos); + return editor.edit(edit => { + checkboxes.OrgToggleCheckbox(editor, edit); + }); + }).then(() => { + var actual = textDocument.lineAt(12).text; + assert.equal(actual, expected); + }).then(done, done); + }); }); From 36554f85127a2f2b616ddfdf7396078fe83a2c5d Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Tue, 9 Oct 2018 13:52:45 -0500 Subject: [PATCH 04/27] Make ticking checkbox test pass. --- src/checkboxes.ts | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/checkboxes.ts b/src/checkboxes.ts index 3862a06..88e0557 100644 --- a/src/checkboxes.ts +++ b/src/checkboxes.ts @@ -15,8 +15,8 @@ export function OrgToggleCheckbox(editor: TextEditor, edit: TextEditorEdit) { let line = doc.lineAt(editor.selection.active.line); let checkbox = orgFindCookie(checkboxRegex, line); if (checkbox) { - let text = doc.getText(checkbox); - var delta = orgCascadeCheckbox(edit, checkbox, line, !orgIsChecked(text)); + let text = doc.getText(checkbox).toLowerCase(); + var delta = orgCascadeCheckbox(edit, checkbox, line, text == 'x' ? ' ' : 'x'); let parent = orgFindParent(editor, line); // Since the updates as a result of toggle have not happened yet in the editor, counting checked children is going to use old value of current checkbox. Hence the adjustment. orgUpdateParent(editor, edit, parent, delta); @@ -29,10 +29,6 @@ export function OrgUpdateSummary(editor: TextEditor, edit: TextEditorEdit) { orgUpdateParent(editor, edit, line, 0); } -function orgIsChecked(value: string): boolean { - return value == 'x' || value == 'X'; -} - function orgFindCookie(cookie: RegExp, line: TextLine): Range | undefined { let match = cookie.exec(line.text); if (match) { @@ -40,6 +36,18 @@ function orgFindCookie(cookie: RegExp, line: TextLine): Range | undefined { } return undefined; } + +function orgTriStateToDelta(value: string): number { + switch (value) { + case 'x': return 1; + case ' ': return -1; + default: return 0; + } +} + +function orgGetTriState(checked, total: number): string { + return checked == 0 ? ' ' : (checked == total ? 'x' : '-'); +} // Calculate and return indentation level of the line. Used in traversing nested lists and locating parent item. function orgGetIndent(line: TextLine): number { @@ -52,19 +60,18 @@ function orgGetIndent(line: TextLine): number { } // Perform the toggle. 'x' or 'X' becomes blank and blank becomes 'X'. -function orgCascadeCheckbox(edit: TextEditorEdit, checkbox: Range, line: TextLine, toCheck: boolean): number { +function orgCascadeCheckbox(edit: TextEditorEdit, checkbox: Range, line: TextLine, toCheck: string): number { if (!checkbox) { return 0; } let editor = window.activeTextEditor; - let text = editor.document.getText(checkbox); - let isChecked = orgIsChecked(text); - if (isChecked == toCheck) { + let text = editor.document.getText(checkbox).toLowerCase(); + if (text == toCheck) { return 0; // Nothing to do. } - edit.replace(checkbox, toCheck ? 'X' : ' '); + edit.replace(checkbox, toCheck); if (!line) { - return toCheck ? 1 : -1; + return orgTriStateToDelta(toCheck); } let children = orgFindChildren(editor, line); let child: TextLine = null; @@ -79,10 +86,10 @@ function orgCascadeCheckbox(edit: TextEditorEdit, checkbox: Range, line: TextLin } let percent = orgFindCookie(percentRegex, line); if (percent) { - total = toCheck ? 100 : 0; + total = toCheck == 'x' ? 100 : 0; edit.replace(percent, total.toString()); } - return toCheck ? 1 : -1; + return orgTriStateToDelta(toCheck); } // Find parent item by walking lines up to the start of the file looking for a smaller indentation. Does not ignore blank lines (indentation 0). @@ -117,7 +124,7 @@ function orgUpdateParent(editor: TextEditor, edit: TextEditorEdit, line: TextLin let doc = editor.document; for (let child of children) { chk = orgFindCookie(checkboxRegex, child); - if (orgIsChecked(doc.getText(chk))) { + if (doc.getText(chk).toLowerCase() == 'x') { checked++; } } @@ -132,7 +139,7 @@ function orgUpdateParent(editor: TextEditor, edit: TextEditorEdit, line: TextLin // If there is a checkbox on this line, update it depending on (checked == total). chk = orgFindCookie(checkboxRegex, line); // Prevent propagation downstream by passing line = null. - let delta = orgCascadeCheckbox(edit, chk, null, checked == total); + let delta = orgCascadeCheckbox(edit, chk, null, orgGetTriState(checked, total)); // Recursively update parent nodes let parent = orgFindParent(editor, line); // Since the updates as a result of toggle have not happened yet in the editor, counting checked children is going to use old value of current checkbox. Hence the adjustment. From c3937a657559ef6c2c59d8b2166ebb731ee31ffe Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Tue, 9 Oct 2018 14:03:44 -0500 Subject: [PATCH 05/27] Added test ticking parent checkbox ticks all children --- test/checkboxes.test.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/checkboxes.test.ts b/test/checkboxes.test.ts index d4d61a0..0265839 100644 --- a/test/checkboxes.test.ts +++ b/test/checkboxes.test.ts @@ -43,4 +43,29 @@ suite('Checkboxes', () => { assert.equal(actual, expected); }).then(done, done); }); + test('Ticking parent checkbox ticks all children', done => { + const filePath = join(__dirname, '../../test/fixtures/checkboxes.org'); + const expected = [ + ' - [x] updates parent summary/percent cookie', + ' - [x] sets parent to on if all children are on', + ' - [x] sets parent to off when all children are off', + ' - [x] sets parent to undetermined when some children are on and some are off' + ]; + let textDocument: TextDocument; + workspace.openTextDocument(filePath).then(document => { + textDocument = document; + return window.showTextDocument(document); + }).then(editor => { + const pos = new Position(12, 14); + editor.selection = new Selection(pos, pos); + return editor.edit(edit => { + checkboxes.OrgToggleCheckbox(editor, edit); + }); + }).then(() => { + for (var i: number = 0; i < 4; i++) { + var actual = textDocument.lineAt(13 + i).text; + assert.equal(actual, expected[i]); + } + }).then(done, done); + }); }); From 4e4e0a9f2e22c58fedb926caafb86585c0f442e6 Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Tue, 9 Oct 2018 14:33:17 -0500 Subject: [PATCH 06/27] Added test for unticking last ticked child checkbox --- test/checkboxes.test.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/checkboxes.test.ts b/test/checkboxes.test.ts index 0265839..8a9ca61 100644 --- a/test/checkboxes.test.ts +++ b/test/checkboxes.test.ts @@ -68,4 +68,22 @@ suite('Checkboxes', () => { } }).then(done, done); }); + test('Unticking last ticked child clears parent checkbox', done => { + const filePath = join(__dirname, '../../test/fixtures/checkboxes.org'); + let expected = ' - [ ] call people [0/3]'; + let textDocument: TextDocument; + workspace.openTextDocument(filePath).then(document => { + textDocument = document; + return window.showTextDocument(document); + }).then(editor => { + const pos = new Position(3, 14); + editor.selection = new Selection(pos, pos); + return editor.edit(edit => { + checkboxes.OrgToggleCheckbox(editor, edit); + }); + }).then(() => { + var actual = textDocument.lineAt(1).text; + assert.equal(actual, expected); + }).then(done, done); + }); }); From d17fbfd15b347b220f095ebd117ecaf908f94c1e Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Tue, 9 Oct 2018 15:41:55 -0500 Subject: [PATCH 07/27] Shortcicuit checking for parent nodes whenever possible --- src/checkboxes.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/checkboxes.ts b/src/checkboxes.ts index 88e0557..05ecc3a 100644 --- a/src/checkboxes.ts +++ b/src/checkboxes.ts @@ -19,7 +19,9 @@ export function OrgToggleCheckbox(editor: TextEditor, edit: TextEditorEdit) { var delta = orgCascadeCheckbox(edit, checkbox, line, text == 'x' ? ' ' : 'x'); let parent = orgFindParent(editor, line); // Since the updates as a result of toggle have not happened yet in the editor, counting checked children is going to use old value of current checkbox. Hence the adjustment. - orgUpdateParent(editor, edit, parent, delta); + if (parent) { + orgUpdateParent(editor, edit, parent, delta); + } } } @@ -143,7 +145,9 @@ function orgUpdateParent(editor: TextEditor, edit: TextEditorEdit, line: TextLin // Recursively update parent nodes let parent = orgFindParent(editor, line); // Since the updates as a result of toggle have not happened yet in the editor, counting checked children is going to use old value of current checkbox. Hence the adjustment. - orgUpdateParent(editor, edit, parent, delta); + if (parent) { + orgUpdateParent(editor, edit, parent, delta); + } } // Find parent item by walking lines up to the start of the file looking for a smaller indentation. Does not ignore blank lines (indentation 0). From b98f43426a573305b8634d2bb489c7c8bfdace13 Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Tue, 9 Oct 2018 15:44:08 -0500 Subject: [PATCH 08/27] Added test for ticking all child checkboxes --- test/checkboxes.test.ts | 28 +++++++++++++++++++++++++++- test/fixtures/checkboxes.org | 4 ++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/test/checkboxes.test.ts b/test/checkboxes.test.ts index 8a9ca61..88e307d 100644 --- a/test/checkboxes.test.ts +++ b/test/checkboxes.test.ts @@ -2,7 +2,7 @@ import 'mocha'; import * as assert from 'assert'; -import { Position, Selection, TextDocument, window, workspace } from 'vscode'; +import { Position, Selection, TextDocument, window, workspace, TextEditor } from 'vscode'; import { join } from 'path'; import * as checkboxes from '../src/checkboxes'; @@ -86,4 +86,30 @@ suite('Checkboxes', () => { assert.equal(actual, expected); }).then(done, done); }); + test('Ticking all children ticks parent checkbox', done => { + const filePath = join(__dirname, '../../test/fixtures/checkboxes.org'); + let expected = ' - [x] toggling parent checkbox [3/3]'; + let textDocument: TextDocument; + let textEditor: TextEditor; + workspace.openTextDocument(filePath).then(document => { + textDocument = document; + return window.showTextDocument(document); + }).then(editor => { + textEditor = editor; + var pos = new Position(18, 5); + editor.selection = new Selection(pos, pos); + return editor.edit(edit => { + checkboxes.OrgToggleCheckbox(editor, edit); + }); + }).then(() => { + var pos = new Position(20, 5); + textEditor.selection = new Selection(pos, pos); + return textEditor.edit(edit => { + checkboxes.OrgToggleCheckbox(textEditor, edit); + }); + }).then(() => { + var actual = textDocument.lineAt(17).text; + assert.equal(actual, expected); + }).then(done, done); + }); }); diff --git a/test/fixtures/checkboxes.org b/test/fixtures/checkboxes.org index e122e2b..805bcca 100644 --- a/test/fixtures/checkboxes.org +++ b/test/fixtures/checkboxes.org @@ -15,7 +15,7 @@ - [ ] sets parent to on if all children are on - [ ] sets parent to off when all children are off - [ ] sets parent to undetermined when some children are on and some are off - - [ ] toggling parent checkbox [/] + - [-] toggling parent checkbox [/] - [ ] updates summary/percent cookie - - [ ] sets all children to on when parent is on + - [x] sets all children to on when parent is on - [ ] sets all children to off when parent is off From a394e2824376498a95b6d2dc8ca8933b6d092c78 Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Tue, 9 Oct 2018 16:02:53 -0500 Subject: [PATCH 09/27] Use undefined instead of null --- src/checkboxes.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/checkboxes.ts b/src/checkboxes.ts index 05ecc3a..01699e9 100644 --- a/src/checkboxes.ts +++ b/src/checkboxes.ts @@ -76,7 +76,7 @@ function orgCascadeCheckbox(edit: TextEditorEdit, checkbox: Range, line: TextLin return orgTriStateToDelta(toCheck); } let children = orgFindChildren(editor, line); - let child: TextLine = null; + let child: TextLine = undefined; for (child of children) { orgCascadeCheckbox(edit, orgFindCookie(checkboxRegex, child), child, toCheck); } @@ -95,7 +95,7 @@ function orgCascadeCheckbox(edit: TextEditorEdit, checkbox: Range, line: TextLin } // Find parent item by walking lines up to the start of the file looking for a smaller indentation. Does not ignore blank lines (indentation 0). -function orgFindParent(editor: TextEditor, line: TextLine): TextLine { +function orgFindParent(editor: TextEditor, line: TextLine): TextLine | undefined { let lnum = line.lineNumber; let indent = orgGetIndent(line); let parent = null; @@ -104,7 +104,7 @@ function orgFindParent(editor: TextEditor, line: TextLine): TextLine { while (pindent >= indent) { lnum--; if (lnum < 0) { - return null; + return undefined; } parent = doc.lineAt(lnum); @@ -122,7 +122,7 @@ function orgUpdateParent(editor: TextEditor, edit: TextEditorEdit, line: TextLin let children = orgFindChildren(editor, line); let total = children.length; let checked = adjust; - let chk = null; + let chk: Range = undefined; let doc = editor.document; for (let child of children) { chk = orgFindCookie(checkboxRegex, child); @@ -141,7 +141,7 @@ function orgUpdateParent(editor: TextEditor, edit: TextEditorEdit, line: TextLin // If there is a checkbox on this line, update it depending on (checked == total). chk = orgFindCookie(checkboxRegex, line); // Prevent propagation downstream by passing line = null. - let delta = orgCascadeCheckbox(edit, chk, null, orgGetTriState(checked, total)); + let delta = orgCascadeCheckbox(edit, chk, undefined, orgGetTriState(checked, total)); // Recursively update parent nodes let parent = orgFindParent(editor, line); // Since the updates as a result of toggle have not happened yet in the editor, counting checked children is going to use old value of current checkbox. Hence the adjustment. @@ -157,7 +157,7 @@ function orgFindChildren(editor: TextEditor, line: TextLine): TextLine[] { let doc = editor.document; let lmax = doc.lineCount - 1; let indent = orgGetIndent(line); - let child: TextLine = null; + let child: TextLine = undefined; let cindent = indent; let next_indent = -1; while (lnum < lmax) { From 80aed16d274e902f6f2e832c6c7fc200a5577258 Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Tue, 9 Oct 2018 16:38:59 -0500 Subject: [PATCH 10/27] Added tabs to spaces conversion with tests --- src/checkboxes.ts | 19 ++++++++++++++++--- test/checkboxes.test.ts | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/checkboxes.ts b/src/checkboxes.ts index 01699e9..ef079d0 100644 --- a/src/checkboxes.ts +++ b/src/checkboxes.ts @@ -51,12 +51,25 @@ function orgGetTriState(checked, total: number): string { return checked == 0 ? ' ' : (checked == total ? 'x' : '-'); } +export function OrgTabsToSpaces(tabs: string): number { + if (!tabs) + return 0; + const tabWidth = 4; + let off = 1; + for (let i = 0; i < tabs.length; i++) { + if (tabs[i] == '\t') + off += tabWidth - off % tabWidth; + else + off++; + } + return off; +} + // Calculate and return indentation level of the line. Used in traversing nested lists and locating parent item. function orgGetIndent(line: TextLine): number { let match = indentRegex.exec(line.text); if (match) { - // TODO: Convert tabs to spaces? - return match[1].length; + return OrgTabsToSpaces(match[1]); } return 0; } @@ -96,11 +109,11 @@ function orgCascadeCheckbox(edit: TextEditorEdit, checkbox: Range, line: TextLin // Find parent item by walking lines up to the start of the file looking for a smaller indentation. Does not ignore blank lines (indentation 0). function orgFindParent(editor: TextEditor, line: TextLine): TextLine | undefined { + let doc = editor.document; let lnum = line.lineNumber; let indent = orgGetIndent(line); let parent = null; let pindent = indent; - let doc = editor.document; while (pindent >= indent) { lnum--; if (lnum < 0) { diff --git a/test/checkboxes.test.ts b/test/checkboxes.test.ts index 88e307d..0292172 100644 --- a/test/checkboxes.test.ts +++ b/test/checkboxes.test.ts @@ -7,6 +7,25 @@ import { join } from 'path'; import * as checkboxes from '../src/checkboxes'; suite('Checkboxes', () => { + test('Can convert tabs to spaces', done => { + let cases = [ + " \t \t ", + "\t\t", + "\t \t", + " \t \t " + ]; + let expected = [ + 9, + 8, + 8, + 14 + ]; + + for (let i = 0; i < cases.length; i++) { + assert.equal(checkboxes.OrgTabsToSpaces(cases[i]), expected[i]); + } + done(); + }); test('Can update summary', done => { const filePath = join(__dirname, '../../test/fixtures/checkboxes.org'); let expected = '* TODO Implement tests [0%] [0/4]'; From 5143acdb0785bf87f1e540cedeb1bc05464bb76a Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Tue, 9 Oct 2018 16:49:28 -0500 Subject: [PATCH 11/27] One more null to undefined replacement --- src/checkboxes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/checkboxes.ts b/src/checkboxes.ts index ef079d0..3580c83 100644 --- a/src/checkboxes.ts +++ b/src/checkboxes.ts @@ -112,7 +112,7 @@ function orgFindParent(editor: TextEditor, line: TextLine): TextLine | undefined let doc = editor.document; let lnum = line.lineNumber; let indent = orgGetIndent(line); - let parent = null; + let parent = undefined; let pindent = indent; while (pindent >= indent) { lnum--; From de0030a44a3df08cdb180e13456b3af772b2404a Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Wed, 10 Oct 2018 09:41:47 -0500 Subject: [PATCH 12/27] Refactor movement and selection in tests --- test/checkboxes.test.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/checkboxes.test.ts b/test/checkboxes.test.ts index 0292172..f7e4be4 100644 --- a/test/checkboxes.test.ts +++ b/test/checkboxes.test.ts @@ -6,6 +6,12 @@ import { Position, Selection, TextDocument, window, workspace, TextEditor } from import { join } from 'path'; import * as checkboxes from '../src/checkboxes'; +function moveAndSelect(editor: TextEditor, line: number, col: number, lineTo?: number, colTo?: number) { + lineTo = lineTo ? lineTo : line; + colTo = colTo ? colTo : col; + editor.selection = new Selection(line, col, lineTo, colTo); +} + suite('Checkboxes', () => { test('Can convert tabs to spaces', done => { let cases = [ @@ -34,8 +40,7 @@ suite('Checkboxes', () => { textDocument = document; return window.showTextDocument(document); }).then(editor => { - const pos = new Position(9, 5); - editor.selection = new Selection(pos, pos); + moveAndSelect(editor, 9, 5); return editor.edit(edit => { checkboxes.OrgUpdateSummary(editor, edit); }); @@ -52,8 +57,7 @@ suite('Checkboxes', () => { textDocument = document; return window.showTextDocument(document); }).then(editor => { - const pos = new Position(14, 14); - editor.selection = new Selection(pos, pos); + moveAndSelect(editor, 14, 14); return editor.edit(edit => { checkboxes.OrgToggleCheckbox(editor, edit); }); @@ -75,8 +79,7 @@ suite('Checkboxes', () => { textDocument = document; return window.showTextDocument(document); }).then(editor => { - const pos = new Position(12, 14); - editor.selection = new Selection(pos, pos); + moveAndSelect(editor, 12, 14); return editor.edit(edit => { checkboxes.OrgToggleCheckbox(editor, edit); }); @@ -95,8 +98,7 @@ suite('Checkboxes', () => { textDocument = document; return window.showTextDocument(document); }).then(editor => { - const pos = new Position(3, 14); - editor.selection = new Selection(pos, pos); + moveAndSelect(editor, 3, 14); return editor.edit(edit => { checkboxes.OrgToggleCheckbox(editor, edit); }); @@ -115,14 +117,12 @@ suite('Checkboxes', () => { return window.showTextDocument(document); }).then(editor => { textEditor = editor; - var pos = new Position(18, 5); - editor.selection = new Selection(pos, pos); + moveAndSelect(editor, 18, 5); return editor.edit(edit => { checkboxes.OrgToggleCheckbox(editor, edit); }); }).then(() => { - var pos = new Position(20, 5); - textEditor.selection = new Selection(pos, pos); + moveAndSelect(textEditor, 20, 5); return textEditor.edit(edit => { checkboxes.OrgToggleCheckbox(textEditor, edit); }); From 3e13344c244ddd787cdb17e1694fdcf2097b9fb2 Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Wed, 10 Oct 2018 09:45:58 -0500 Subject: [PATCH 13/27] Parameterized tab expansion function with optional tab size --- src/checkboxes.ts | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/checkboxes.ts b/src/checkboxes.ts index 3580c83..3239e2f 100644 --- a/src/checkboxes.ts +++ b/src/checkboxes.ts @@ -9,6 +9,19 @@ const summaryRegex = /\[(\d*\/\d*)\]/; // Percentage is a cookie indicating the percentage of ticked checkboxes in the child list relative to the total number of checkboxes in the list. const percentRegex = /\[(\d*)%\]/; const indentRegex = /^(\s*)\S/; + +export function OrgTabsToSpaces(tabs: string, tabSize: number = 4): number { + if (!tabs) + return 0; + let off = 1; + for (let i = 0; i < tabs.length; i++) { + if (tabs[i] == '\t') + off += tabSize - off % tabSize; + else + off++; + } + return off; +} export function OrgToggleCheckbox(editor: TextEditor, edit: TextEditorEdit) { let doc = editor.document; @@ -50,20 +63,6 @@ function orgTriStateToDelta(value: string): number { function orgGetTriState(checked, total: number): string { return checked == 0 ? ' ' : (checked == total ? 'x' : '-'); } - -export function OrgTabsToSpaces(tabs: string): number { - if (!tabs) - return 0; - const tabWidth = 4; - let off = 1; - for (let i = 0; i < tabs.length; i++) { - if (tabs[i] == '\t') - off += tabWidth - off % tabWidth; - else - off++; - } - return off; -} // Calculate and return indentation level of the line. Used in traversing nested lists and locating parent item. function orgGetIndent(line: TextLine): number { From 62f311176f4ab977bc528ef806bb9a552afa037a Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Wed, 10 Oct 2018 10:00:42 -0500 Subject: [PATCH 14/27] Use string content instead of fixture in tests --- test/checkboxes.test.ts | 46 ++++++++++++++++++++++++++---------- test/fixtures/checkboxes.org | 21 ---------------- 2 files changed, 34 insertions(+), 33 deletions(-) delete mode 100644 test/fixtures/checkboxes.org diff --git a/test/checkboxes.test.ts b/test/checkboxes.test.ts index f7e4be4..d36d387 100644 --- a/test/checkboxes.test.ts +++ b/test/checkboxes.test.ts @@ -2,16 +2,43 @@ import 'mocha'; import * as assert from 'assert'; -import { Position, Selection, TextDocument, window, workspace, TextEditor } from 'vscode'; -import { join } from 'path'; +import { Selection, TextDocument, window, workspace, TextEditor } from 'vscode'; import * as checkboxes from '../src/checkboxes'; +const content: string = +`* TODO Organize party [/] + - [-] call people [/] + - [ ] Peter + - [X] Sarah + - [ ] Sam + - [X] order food + - [ ] think about what music to play + - [X] talk to the neighbors + +* TODO Implement tests [%] [/] + - [ ] updates summary cookie + - [ ] updates percent cookie + - [ ] toggling child checkbox [%] + - [ ] updates parent summary/percent cookie + - [ ] sets parent to on if all children are on + - [ ] sets parent to off when all children are off + - [ ] sets parent to undetermined when some children are on and some are off + - [-] toggling parent checkbox [/] + - [ ] updates summary/percent cookie + - [x] sets all children to on when parent is on + - [ ] sets all children to off when parent is off +`; + function moveAndSelect(editor: TextEditor, line: number, col: number, lineTo?: number, colTo?: number) { lineTo = lineTo ? lineTo : line; colTo = colTo ? colTo : col; editor.selection = new Selection(line, col, lineTo, colTo); } +function loadContent(): Thenable { + return workspace.openTextDocument({ language: 'org', content: content }); +} + suite('Checkboxes', () => { test('Can convert tabs to spaces', done => { let cases = [ @@ -33,10 +60,9 @@ suite('Checkboxes', () => { done(); }); test('Can update summary', done => { - const filePath = join(__dirname, '../../test/fixtures/checkboxes.org'); let expected = '* TODO Implement tests [0%] [0/4]'; let textDocument: TextDocument; - workspace.openTextDocument(filePath).then(document => { + loadContent().then(document => { textDocument = document; return window.showTextDocument(document); }).then(editor => { @@ -50,10 +76,9 @@ suite('Checkboxes', () => { }).then(done, done); }); test('Ticking checkbox updates parent', done => { - const filePath = join(__dirname, '../../test/fixtures/checkboxes.org'); let expected = ' - [-] toggling child checkbox [25%]'; let textDocument: TextDocument; - workspace.openTextDocument(filePath).then(document => { + loadContent().then(document => { textDocument = document; return window.showTextDocument(document); }).then(editor => { @@ -67,7 +92,6 @@ suite('Checkboxes', () => { }).then(done, done); }); test('Ticking parent checkbox ticks all children', done => { - const filePath = join(__dirname, '../../test/fixtures/checkboxes.org'); const expected = [ ' - [x] updates parent summary/percent cookie', ' - [x] sets parent to on if all children are on', @@ -75,7 +99,7 @@ suite('Checkboxes', () => { ' - [x] sets parent to undetermined when some children are on and some are off' ]; let textDocument: TextDocument; - workspace.openTextDocument(filePath).then(document => { + loadContent().then(document => { textDocument = document; return window.showTextDocument(document); }).then(editor => { @@ -91,10 +115,9 @@ suite('Checkboxes', () => { }).then(done, done); }); test('Unticking last ticked child clears parent checkbox', done => { - const filePath = join(__dirname, '../../test/fixtures/checkboxes.org'); let expected = ' - [ ] call people [0/3]'; let textDocument: TextDocument; - workspace.openTextDocument(filePath).then(document => { + loadContent().then(document => { textDocument = document; return window.showTextDocument(document); }).then(editor => { @@ -108,11 +131,10 @@ suite('Checkboxes', () => { }).then(done, done); }); test('Ticking all children ticks parent checkbox', done => { - const filePath = join(__dirname, '../../test/fixtures/checkboxes.org'); let expected = ' - [x] toggling parent checkbox [3/3]'; let textDocument: TextDocument; let textEditor: TextEditor; - workspace.openTextDocument(filePath).then(document => { + loadContent().then(document => { textDocument = document; return window.showTextDocument(document); }).then(editor => { diff --git a/test/fixtures/checkboxes.org b/test/fixtures/checkboxes.org deleted file mode 100644 index 805bcca..0000000 --- a/test/fixtures/checkboxes.org +++ /dev/null @@ -1,21 +0,0 @@ -* TODO Organize party [/] - - [-] call people [/] - - [ ] Peter - - [X] Sarah - - [ ] Sam - - [X] order food - - [ ] think about what music to play - - [X] talk to the neighbors - -* TODO Implement tests [%] [/] - - [ ] updates summary cookie - - [ ] updates percent cookie - - [ ] toggling child checkbox [%] - - [ ] updates parent summary/percent cookie - - [ ] sets parent to on if all children are on - - [ ] sets parent to off when all children are off - - [ ] sets parent to undetermined when some children are on and some are off - - [-] toggling parent checkbox [/] - - [ ] updates summary/percent cookie - - [x] sets all children to on when parent is on - - [ ] sets all children to off when parent is off From f3792524fa0110c5e58b04a530d90f42115e8e7b Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Wed, 10 Oct 2018 10:20:07 -0500 Subject: [PATCH 15/27] Refactor tests for correctness --- test/checkboxes.test.ts | 43 ++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/test/checkboxes.test.ts b/test/checkboxes.test.ts index d36d387..ff14d03 100644 --- a/test/checkboxes.test.ts +++ b/test/checkboxes.test.ts @@ -2,7 +2,7 @@ import 'mocha'; import * as assert from 'assert'; -import { Selection, TextDocument, window, workspace, TextEditor } from 'vscode'; +import * as vscode from 'vscode'; import * as checkboxes from '../src/checkboxes'; const content: string = @@ -29,17 +29,23 @@ const content: string = - [ ] sets all children to off when parent is off `; -function moveAndSelect(editor: TextEditor, line: number, col: number, lineTo?: number, colTo?: number) { +function closeAllEditors(): Thenable { + return vscode.commands.executeCommand('workbench.action.closeAllEditors'); +} + +function moveAndSelect(editor: vscode.TextEditor, line: number, col: number, lineTo?: number, colTo?: number) { lineTo = lineTo ? lineTo : line; colTo = colTo ? colTo : col; - editor.selection = new Selection(line, col, lineTo, colTo); + editor.selection = new vscode.Selection(line, col, lineTo, colTo); } -function loadContent(): Thenable { - return workspace.openTextDocument({ language: 'org', content: content }); +function loadContent(): Thenable { + return vscode.workspace.openTextDocument({ language: 'org', content: content }); } suite('Checkboxes', () => { + teardown(closeAllEditors); + test('Can convert tabs to spaces', done => { let cases = [ " \t \t ", @@ -61,10 +67,10 @@ suite('Checkboxes', () => { }); test('Can update summary', done => { let expected = '* TODO Implement tests [0%] [0/4]'; - let textDocument: TextDocument; + let textDocument: vscode.TextDocument; loadContent().then(document => { textDocument = document; - return window.showTextDocument(document); + return vscode.window.showTextDocument(document); }).then(editor => { moveAndSelect(editor, 9, 5); return editor.edit(edit => { @@ -73,14 +79,15 @@ suite('Checkboxes', () => { }).then(() => { var actual = textDocument.lineAt(9).text; assert.equal(actual, expected); + return Promise.resolve(); }).then(done, done); }); test('Ticking checkbox updates parent', done => { let expected = ' - [-] toggling child checkbox [25%]'; - let textDocument: TextDocument; + let textDocument: vscode.TextDocument; loadContent().then(document => { textDocument = document; - return window.showTextDocument(document); + return vscode.window.showTextDocument(document); }).then(editor => { moveAndSelect(editor, 14, 14); return editor.edit(edit => { @@ -89,6 +96,7 @@ suite('Checkboxes', () => { }).then(() => { var actual = textDocument.lineAt(12).text; assert.equal(actual, expected); + return Promise.resolve(); }).then(done, done); }); test('Ticking parent checkbox ticks all children', done => { @@ -98,10 +106,10 @@ suite('Checkboxes', () => { ' - [x] sets parent to off when all children are off', ' - [x] sets parent to undetermined when some children are on and some are off' ]; - let textDocument: TextDocument; + let textDocument: vscode.TextDocument; loadContent().then(document => { textDocument = document; - return window.showTextDocument(document); + return vscode.window.showTextDocument(document); }).then(editor => { moveAndSelect(editor, 12, 14); return editor.edit(edit => { @@ -112,14 +120,15 @@ suite('Checkboxes', () => { var actual = textDocument.lineAt(13 + i).text; assert.equal(actual, expected[i]); } + return Promise.resolve(); }).then(done, done); }); test('Unticking last ticked child clears parent checkbox', done => { let expected = ' - [ ] call people [0/3]'; - let textDocument: TextDocument; + let textDocument: vscode.TextDocument; loadContent().then(document => { textDocument = document; - return window.showTextDocument(document); + return vscode.window.showTextDocument(document); }).then(editor => { moveAndSelect(editor, 3, 14); return editor.edit(edit => { @@ -128,15 +137,16 @@ suite('Checkboxes', () => { }).then(() => { var actual = textDocument.lineAt(1).text; assert.equal(actual, expected); + return Promise.resolve(); }).then(done, done); }); test('Ticking all children ticks parent checkbox', done => { let expected = ' - [x] toggling parent checkbox [3/3]'; - let textDocument: TextDocument; - let textEditor: TextEditor; + let textDocument: vscode.TextDocument; + let textEditor: vscode.TextEditor; loadContent().then(document => { textDocument = document; - return window.showTextDocument(document); + return vscode.window.showTextDocument(document); }).then(editor => { textEditor = editor; moveAndSelect(editor, 18, 5); @@ -151,6 +161,7 @@ suite('Checkboxes', () => { }).then(() => { var actual = textDocument.lineAt(17).text; assert.equal(actual, expected); + return Promise.resolve(); }).then(done, done); }); }); From fb4a40013579e1a0ce8999d5f2ab97d356f97a54 Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Wed, 10 Oct 2018 10:24:42 -0500 Subject: [PATCH 16/27] Added testing for expansion of 8-space tabs --- test/checkboxes.test.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/checkboxes.test.ts b/test/checkboxes.test.ts index ff14d03..9cbc373 100644 --- a/test/checkboxes.test.ts +++ b/test/checkboxes.test.ts @@ -53,15 +53,22 @@ suite('Checkboxes', () => { "\t \t", " \t \t " ]; - let expected = [ + let expected4 = [ 9, 8, 8, 14 ]; + let expected8 = [ + 17, + 16, + 16, + 18 + ]; for (let i = 0; i < cases.length; i++) { - assert.equal(checkboxes.OrgTabsToSpaces(cases[i]), expected[i]); + assert.equal(checkboxes.OrgTabsToSpaces(cases[i], 4), expected4[i]); + assert.equal(checkboxes.OrgTabsToSpaces(cases[i], 8), expected8[i]); } done(); }); From 93cc88424a4b73a70c8a0a5e96d080efa93e7786 Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Wed, 10 Oct 2018 11:11:49 -0500 Subject: [PATCH 17/27] Use async instead of promises in tests --- test/checkboxes.test.ts | 133 +++++++++++++++------------------------- 1 file changed, 49 insertions(+), 84 deletions(-) diff --git a/test/checkboxes.test.ts b/test/checkboxes.test.ts index 9cbc373..2b07a47 100644 --- a/test/checkboxes.test.ts +++ b/test/checkboxes.test.ts @@ -72,103 +72,68 @@ suite('Checkboxes', () => { } done(); }); - test('Can update summary', done => { + test('Can update summary', async () => { let expected = '* TODO Implement tests [0%] [0/4]'; - let textDocument: vscode.TextDocument; - loadContent().then(document => { - textDocument = document; - return vscode.window.showTextDocument(document); - }).then(editor => { - moveAndSelect(editor, 9, 5); - return editor.edit(edit => { - checkboxes.OrgUpdateSummary(editor, edit); - }); - }).then(() => { - var actual = textDocument.lineAt(9).text; - assert.equal(actual, expected); - return Promise.resolve(); - }).then(done, done); + let document = await loadContent(); + let editor = await vscode.window.showTextDocument(document); + let registration = vscode.commands.registerTextEditorCommand('org.updateSummary', checkboxes.OrgUpdateSummary); + moveAndSelect(editor, 9, 5); + await vscode.commands.executeCommand('org.updateSummary'); + var actual = document.lineAt(9).text; + assert.equal(actual, expected); + registration.dispose(); }); - test('Ticking checkbox updates parent', done => { + test('Ticking checkbox updates parent', async () => { let expected = ' - [-] toggling child checkbox [25%]'; - let textDocument: vscode.TextDocument; - loadContent().then(document => { - textDocument = document; - return vscode.window.showTextDocument(document); - }).then(editor => { - moveAndSelect(editor, 14, 14); - return editor.edit(edit => { - checkboxes.OrgToggleCheckbox(editor, edit); - }); - }).then(() => { - var actual = textDocument.lineAt(12).text; - assert.equal(actual, expected); - return Promise.resolve(); - }).then(done, done); + let document = await loadContent(); + let editor = await vscode.window.showTextDocument(document); + let registration = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', checkboxes.OrgToggleCheckbox); + moveAndSelect(editor, 14, 14); + await vscode.commands.executeCommand('org.toggleCheckbox'); + var actual = document.lineAt(12).text; + assert.equal(actual, expected); + registration.dispose(); }); - test('Ticking parent checkbox ticks all children', done => { + test('Ticking parent checkbox ticks all children', async () => { const expected = [ ' - [x] updates parent summary/percent cookie', ' - [x] sets parent to on if all children are on', ' - [x] sets parent to off when all children are off', ' - [x] sets parent to undetermined when some children are on and some are off' ]; - let textDocument: vscode.TextDocument; - loadContent().then(document => { - textDocument = document; - return vscode.window.showTextDocument(document); - }).then(editor => { - moveAndSelect(editor, 12, 14); - return editor.edit(edit => { - checkboxes.OrgToggleCheckbox(editor, edit); - }); - }).then(() => { - for (var i: number = 0; i < 4; i++) { - var actual = textDocument.lineAt(13 + i).text; - assert.equal(actual, expected[i]); - } - return Promise.resolve(); - }).then(done, done); + let document = await loadContent(); + let editor = await vscode.window.showTextDocument(document); + let registration = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', checkboxes.OrgToggleCheckbox); + moveAndSelect(editor, 12, 14); + await vscode.commands.executeCommand('org.toggleCheckbox'); + for (var i: number = 0; i < 4; i++) { + var actual = document.lineAt(13 + i).text; + assert.equal(actual, expected[i]); + } + registration.dispose(); }); - test('Unticking last ticked child clears parent checkbox', done => { + test('Unticking last ticked child clears parent checkbox', async () => { let expected = ' - [ ] call people [0/3]'; - let textDocument: vscode.TextDocument; - loadContent().then(document => { - textDocument = document; - return vscode.window.showTextDocument(document); - }).then(editor => { - moveAndSelect(editor, 3, 14); - return editor.edit(edit => { - checkboxes.OrgToggleCheckbox(editor, edit); - }); - }).then(() => { - var actual = textDocument.lineAt(1).text; - assert.equal(actual, expected); - return Promise.resolve(); - }).then(done, done); + let document = await loadContent(); + let editor = await vscode.window.showTextDocument(document); + let registration = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', checkboxes.OrgToggleCheckbox); + moveAndSelect(editor, 3, 14); + await vscode.commands.executeCommand('org.toggleCheckbox'); + var actual = document.lineAt(1).text; + assert.equal(actual, expected); + registration.dispose(); }); - test('Ticking all children ticks parent checkbox', done => { + test('Ticking all children ticks parent checkbox', async () => { let expected = ' - [x] toggling parent checkbox [3/3]'; - let textDocument: vscode.TextDocument; - let textEditor: vscode.TextEditor; - loadContent().then(document => { - textDocument = document; - return vscode.window.showTextDocument(document); - }).then(editor => { - textEditor = editor; - moveAndSelect(editor, 18, 5); - return editor.edit(edit => { - checkboxes.OrgToggleCheckbox(editor, edit); - }); - }).then(() => { - moveAndSelect(textEditor, 20, 5); - return textEditor.edit(edit => { - checkboxes.OrgToggleCheckbox(textEditor, edit); - }); - }).then(() => { - var actual = textDocument.lineAt(17).text; - assert.equal(actual, expected); - return Promise.resolve(); - }).then(done, done); + let document = await loadContent(); + let editor = await vscode.window.showTextDocument(document); + let registration = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', checkboxes.OrgToggleCheckbox); + moveAndSelect(editor, 18, 5); + await vscode.commands.executeCommand('org.toggleCheckbox'); + moveAndSelect(editor, 20, 5); + await vscode.commands.executeCommand('org.toggleCheckbox'); + var actual = document.lineAt(17).text; + assert.equal(actual, expected); + registration.dispose(); }); }); From 92455168e7bf8ada4ada65bf34519712f8842363 Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Wed, 10 Oct 2018 12:35:21 -0500 Subject: [PATCH 18/27] Added 3-level checkbox propagation test --- test/checkboxes.test.ts | 46 ++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/test/checkboxes.test.ts b/test/checkboxes.test.ts index 2b07a47..23a199c 100644 --- a/test/checkboxes.test.ts +++ b/test/checkboxes.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; import * as checkboxes from '../src/checkboxes'; -const content: string = +const content2level: string = `* TODO Organize party [/] - [-] call people [/] - [ ] Peter @@ -29,6 +29,21 @@ const content: string = - [ ] sets all children to off when parent is off `; +const content3level: string = +`* TODO Organize party [/] + - [-] order food [%] + - [ ] appetizers + - [-] salads [/] + - [ ] ceasar salad + - [x] coleslaw + - [ ] avocado salad + - [ ] dessert [/] + - [ ] cake + - [ ] cookies + - [ ] icecream + - [x] order drinks +`; + function closeAllEditors(): Thenable { return vscode.commands.executeCommand('workbench.action.closeAllEditors'); } @@ -39,7 +54,7 @@ function moveAndSelect(editor: vscode.TextEditor, line: number, col: number, lin editor.selection = new vscode.Selection(line, col, lineTo, colTo); } -function loadContent(): Thenable { +function loadContent(content: string): Thenable { return vscode.workspace.openTextDocument({ language: 'org', content: content }); } @@ -74,7 +89,7 @@ suite('Checkboxes', () => { }); test('Can update summary', async () => { let expected = '* TODO Implement tests [0%] [0/4]'; - let document = await loadContent(); + let document = await loadContent(content2level); let editor = await vscode.window.showTextDocument(document); let registration = vscode.commands.registerTextEditorCommand('org.updateSummary', checkboxes.OrgUpdateSummary); moveAndSelect(editor, 9, 5); @@ -85,7 +100,7 @@ suite('Checkboxes', () => { }); test('Ticking checkbox updates parent', async () => { let expected = ' - [-] toggling child checkbox [25%]'; - let document = await loadContent(); + let document = await loadContent(content2level); let editor = await vscode.window.showTextDocument(document); let registration = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', checkboxes.OrgToggleCheckbox); moveAndSelect(editor, 14, 14); @@ -101,7 +116,7 @@ suite('Checkboxes', () => { ' - [x] sets parent to off when all children are off', ' - [x] sets parent to undetermined when some children are on and some are off' ]; - let document = await loadContent(); + let document = await loadContent(content2level); let editor = await vscode.window.showTextDocument(document); let registration = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', checkboxes.OrgToggleCheckbox); moveAndSelect(editor, 12, 14); @@ -114,7 +129,7 @@ suite('Checkboxes', () => { }); test('Unticking last ticked child clears parent checkbox', async () => { let expected = ' - [ ] call people [0/3]'; - let document = await loadContent(); + let document = await loadContent(content2level); let editor = await vscode.window.showTextDocument(document); let registration = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', checkboxes.OrgToggleCheckbox); moveAndSelect(editor, 3, 14); @@ -125,7 +140,7 @@ suite('Checkboxes', () => { }); test('Ticking all children ticks parent checkbox', async () => { let expected = ' - [x] toggling parent checkbox [3/3]'; - let document = await loadContent(); + let document = await loadContent(content2level); let editor = await vscode.window.showTextDocument(document); let registration = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', checkboxes.OrgToggleCheckbox); moveAndSelect(editor, 18, 5); @@ -136,4 +151,21 @@ suite('Checkboxes', () => { assert.equal(actual, expected); registration.dispose(); }); + test('Ticking parent checkbox ticks all children (3 level)', async () => { + const expected = [ + ' - [x] salads [3/3]', + ' - [x] cookies' + ]; + const lineNo = [3, 9]; + let document = await loadContent(content3level); + let editor = await vscode.window.showTextDocument(document); + let registration = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', checkboxes.OrgToggleCheckbox); + moveAndSelect(editor, 1, 7); + await vscode.commands.executeCommand('org.toggleCheckbox'); + for (var i: number = 0; i < expected.length; i++) { + var actual = document.lineAt(lineNo[i]).text; + assert.equal(actual, expected[i]); + } + registration.dispose(); + }); }); From 0289813b9880103c37f99a11c0fdddd40aef34db Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Wed, 10 Oct 2018 12:46:25 -0500 Subject: [PATCH 19/27] Fixes formatting, braces, comments, parameter names --- src/checkboxes.ts | 48 ++++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/src/checkboxes.ts b/src/checkboxes.ts index 3239e2f..0f8a4d8 100644 --- a/src/checkboxes.ts +++ b/src/checkboxes.ts @@ -11,14 +11,16 @@ const percentRegex = /\[(\d*)%\]/; const indentRegex = /^(\s*)\S/; export function OrgTabsToSpaces(tabs: string, tabSize: number = 4): number { - if (!tabs) + if (!tabs) { return 0; + } let off = 1; for (let i = 0; i < tabs.length; i++) { - if (tabs[i] == '\t') + if (tabs[i] == '\t') { off += tabSize - off % tabSize; - else + } else { off++; + } } return off; } @@ -31,7 +33,9 @@ export function OrgToggleCheckbox(editor: TextEditor, edit: TextEditorEdit) { let text = doc.getText(checkbox).toLowerCase(); var delta = orgCascadeCheckbox(edit, checkbox, line, text == 'x' ? ' ' : 'x'); let parent = orgFindParent(editor, line); - // Since the updates as a result of toggle have not happened yet in the editor, counting checked children is going to use old value of current checkbox. Hence the adjustment. + // Since the updates as a result of toggle have not happened yet in the editor, + // counting checked children is going to use old value of the current checkbox. + // Hence the delta adjustment. if (parent) { orgUpdateParent(editor, edit, parent, delta); } @@ -44,6 +48,7 @@ export function OrgUpdateSummary(editor: TextEditor, edit: TextEditorEdit) { orgUpdateParent(editor, edit, line, 0); } +// Pattern elements, like ratio summary, percent summary, checkbox, of the orgmode document are called cookies. function orgFindCookie(cookie: RegExp, line: TextLine): Range | undefined { let match = cookie.exec(line.text); if (match) { @@ -73,40 +78,42 @@ function orgGetIndent(line: TextLine): number { return 0; } -// Perform the toggle. 'x' or 'X' becomes blank and blank becomes 'X'. -function orgCascadeCheckbox(edit: TextEditorEdit, checkbox: Range, line: TextLine, toCheck: string): number { +// Set checkbox to the desired state and perform necessary updates to child and parent elements (however many levels). +function orgCascadeCheckbox(edit: TextEditorEdit, checkbox: Range, line: TextLine, state: string): number { if (!checkbox) { return 0; } let editor = window.activeTextEditor; let text = editor.document.getText(checkbox).toLowerCase(); - if (text == toCheck) { + if (text == state) { return 0; // Nothing to do. } - edit.replace(checkbox, toCheck); + edit.replace(checkbox, state); if (!line) { - return orgTriStateToDelta(toCheck); + return orgTriStateToDelta(state); } let children = orgFindChildren(editor, line); let child: TextLine = undefined; for (child of children) { - orgCascadeCheckbox(edit, orgFindCookie(checkboxRegex, child), child, toCheck); + orgCascadeCheckbox(edit, orgFindCookie(checkboxRegex, child), child, state); } - // If there is a summary on this line, update it to either [0/0] or [total/total] depending on value of 'check'. - let total = toCheck ? children.length : 0; + // If there is a summary cookie on this line, update it to either [0/0] or [total/total] depending on target state. + let total = state ? children.length : 0; let summary = orgFindCookie(summaryRegex, line); if (summary) { edit.replace(summary, total.toString() + '/' + total.toString()); } + // If there is a percent cookie on this line, update it to either [0%] or [100%] depending on target state. let percent = orgFindCookie(percentRegex, line); if (percent) { - total = toCheck == 'x' ? 100 : 0; + total = state == 'x' ? 100 : 0; edit.replace(percent, total.toString()); } - return orgTriStateToDelta(toCheck); + return orgTriStateToDelta(state); } -// Find parent item by walking lines up to the start of the file looking for a smaller indentation. Does not ignore blank lines (indentation 0). +// Find parent item by walking lines up to the start of the file looking for a smaller indentation. +// Does not ignore blank lines (indentation 0). function orgFindParent(editor: TextEditor, line: TextLine): TextLine | undefined { let doc = editor.document; let lnum = line.lineNumber; @@ -125,8 +132,8 @@ function orgFindParent(editor: TextEditor, line: TextLine): TextLine | undefined return parent; } -// Update checkbox and summary on this line. Adjust checked items count with an additional offset. That accounts for -// a checkbox that has just been toggled but text in the editor has not been updated yet. +// Update checkbox and summary on this line. Adjust checked items count with an additional offset. +// That accounts for a checkbox that has just been toggled but text in the editor has not been updated yet. function orgUpdateParent(editor: TextEditor, edit: TextEditorEdit, line: TextLine, adjust: number) { if (!line) { return; @@ -156,13 +163,16 @@ function orgUpdateParent(editor: TextEditor, edit: TextEditorEdit, line: TextLin let delta = orgCascadeCheckbox(edit, chk, undefined, orgGetTriState(checked, total)); // Recursively update parent nodes let parent = orgFindParent(editor, line); - // Since the updates as a result of toggle have not happened yet in the editor, counting checked children is going to use old value of current checkbox. Hence the adjustment. + // Since the updates as a result of toggle have not happened yet in the editor, + // counting checked children is going to use old value of the current checkbox. + // Hence the delta adjustment. if (parent) { orgUpdateParent(editor, edit, parent, delta); } } -// Find parent item by walking lines up to the start of the file looking for a smaller indentation. Does not ignore blank lines (indentation 0). +// Find parent item by walking lines up to the start of the file looking for a smaller indentation. +// Does not ignore blank lines (indentation 0). function orgFindChildren(editor: TextEditor, line: TextLine): TextLine[] { let children: TextLine[] = []; let lnum = line.lineNumber; From b3399a648f14864b9a32957e8195dbc571882368 Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Wed, 10 Oct 2018 12:50:07 -0500 Subject: [PATCH 20/27] Replace null with undefined in comment --- src/checkboxes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/checkboxes.ts b/src/checkboxes.ts index 0f8a4d8..f34d2c9 100644 --- a/src/checkboxes.ts +++ b/src/checkboxes.ts @@ -159,7 +159,7 @@ function orgUpdateParent(editor: TextEditor, edit: TextEditorEdit, line: TextLin } // If there is a checkbox on this line, update it depending on (checked == total). chk = orgFindCookie(checkboxRegex, line); - // Prevent propagation downstream by passing line = null. + // Prevent propagation downstream by passing line = undefined. let delta = orgCascadeCheckbox(edit, chk, undefined, orgGetTriState(checked, total)); // Recursively update parent nodes let parent = orgFindParent(editor, line); From 8dd888038c12e7ea0c0775a8448c133329133796 Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Wed, 10 Oct 2018 13:01:28 -0500 Subject: [PATCH 21/27] Update for consistency --- src/checkboxes.ts | 2 +- test/checkboxes.test.ts | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/checkboxes.ts b/src/checkboxes.ts index f34d2c9..abb1ca1 100644 --- a/src/checkboxes.ts +++ b/src/checkboxes.ts @@ -31,7 +31,7 @@ export function OrgToggleCheckbox(editor: TextEditor, edit: TextEditorEdit) { let checkbox = orgFindCookie(checkboxRegex, line); if (checkbox) { let text = doc.getText(checkbox).toLowerCase(); - var delta = orgCascadeCheckbox(edit, checkbox, line, text == 'x' ? ' ' : 'x'); + let delta = orgCascadeCheckbox(edit, checkbox, line, text == 'x' ? ' ' : 'x'); let parent = orgFindParent(editor, line); // Since the updates as a result of toggle have not happened yet in the editor, // counting checked children is going to use old value of the current checkbox. diff --git a/test/checkboxes.test.ts b/test/checkboxes.test.ts index 23a199c..fd129ec 100644 --- a/test/checkboxes.test.ts +++ b/test/checkboxes.test.ts @@ -81,7 +81,7 @@ suite('Checkboxes', () => { 18 ]; - for (let i = 0; i < cases.length; i++) { + for (let i: number = 0; i < cases.length; i++) { assert.equal(checkboxes.OrgTabsToSpaces(cases[i], 4), expected4[i]); assert.equal(checkboxes.OrgTabsToSpaces(cases[i], 8), expected8[i]); } @@ -94,7 +94,7 @@ suite('Checkboxes', () => { let registration = vscode.commands.registerTextEditorCommand('org.updateSummary', checkboxes.OrgUpdateSummary); moveAndSelect(editor, 9, 5); await vscode.commands.executeCommand('org.updateSummary'); - var actual = document.lineAt(9).text; + let actual = document.lineAt(9).text; assert.equal(actual, expected); registration.dispose(); }); @@ -105,7 +105,7 @@ suite('Checkboxes', () => { let registration = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', checkboxes.OrgToggleCheckbox); moveAndSelect(editor, 14, 14); await vscode.commands.executeCommand('org.toggleCheckbox'); - var actual = document.lineAt(12).text; + let actual = document.lineAt(12).text; assert.equal(actual, expected); registration.dispose(); }); @@ -116,13 +116,14 @@ suite('Checkboxes', () => { ' - [x] sets parent to off when all children are off', ' - [x] sets parent to undetermined when some children are on and some are off' ]; + const lineNo = [13, 14, 15, 16]; let document = await loadContent(content2level); let editor = await vscode.window.showTextDocument(document); let registration = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', checkboxes.OrgToggleCheckbox); moveAndSelect(editor, 12, 14); await vscode.commands.executeCommand('org.toggleCheckbox'); - for (var i: number = 0; i < 4; i++) { - var actual = document.lineAt(13 + i).text; + for (let i: number = 0; i < expected.length; i++) { + let actual = document.lineAt(lineNo[i]).text; assert.equal(actual, expected[i]); } registration.dispose(); @@ -134,7 +135,7 @@ suite('Checkboxes', () => { let registration = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', checkboxes.OrgToggleCheckbox); moveAndSelect(editor, 3, 14); await vscode.commands.executeCommand('org.toggleCheckbox'); - var actual = document.lineAt(1).text; + let actual = document.lineAt(1).text; assert.equal(actual, expected); registration.dispose(); }); @@ -147,7 +148,7 @@ suite('Checkboxes', () => { await vscode.commands.executeCommand('org.toggleCheckbox'); moveAndSelect(editor, 20, 5); await vscode.commands.executeCommand('org.toggleCheckbox'); - var actual = document.lineAt(17).text; + let actual = document.lineAt(17).text; assert.equal(actual, expected); registration.dispose(); }); @@ -162,8 +163,8 @@ suite('Checkboxes', () => { let registration = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', checkboxes.OrgToggleCheckbox); moveAndSelect(editor, 1, 7); await vscode.commands.executeCommand('org.toggleCheckbox'); - for (var i: number = 0; i < expected.length; i++) { - var actual = document.lineAt(lineNo[i]).text; + for (let i: number = 0; i < expected.length; i++) { + let actual = document.lineAt(lineNo[i]).text; assert.equal(actual, expected[i]); } registration.dispose(); From dfb91067dc1a82c2be28ca381d14ce2ee10169f1 Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Wed, 10 Oct 2018 13:02:00 -0500 Subject: [PATCH 22/27] Integrate checkboxes features with the extension --- src/extension.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/extension.ts b/src/extension.ts index 5f13fa4..0f277c0 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -9,6 +9,7 @@ import { decrementContext } from './modify-context'; import * as PascuaneseFunctions from './pascuanese-functions'; +import * as Checkboxes from './checkboxes'; import { OrgFoldingProvider } from './org-folding-provider'; export function activate(context: vscode.ExtensionContext) { @@ -35,6 +36,8 @@ export function activate(context: vscode.ExtensionContext) { const verboseCmd = vscode.commands.registerTextEditorCommand('org.verbose', MarkupFunctions.verbose); const literalCmd = vscode.commands.registerTextEditorCommand('org.literal', MarkupFunctions.literal); const butterflyCmd = vscode.commands.registerTextEditorCommand('org.butterfly', PascuaneseFunctions.butterfly); + const updateSummaryCmd = vscode.commands.registerTextEditorCommand('org.updateSummary', Checkboxes.OrgUpdateSummary); + const toggleCheckboxCmd = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', Checkboxes.OrgToggleCheckbox); context.subscriptions.push(insertHeadingRespectContentCmd); context.subscriptions.push(insertChildCmd); @@ -56,6 +59,8 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(verboseCmd); context.subscriptions.push(literalCmd); context.subscriptions.push(butterflyCmd); + context.subscriptions.push(updateSummaryCmd); + context.subscriptions.push(toggleCheckboxCmd); vscode.languages.registerFoldingRangeProvider('org', new OrgFoldingProvider()); } From 57418186de497fd9e58ab9597ef4c8e22ba6dcfe Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Wed, 10 Oct 2018 13:03:55 -0500 Subject: [PATCH 23/27] Remove command registration from tests --- test/checkboxes.test.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/test/checkboxes.test.ts b/test/checkboxes.test.ts index fd129ec..3322280 100644 --- a/test/checkboxes.test.ts +++ b/test/checkboxes.test.ts @@ -91,23 +91,19 @@ suite('Checkboxes', () => { let expected = '* TODO Implement tests [0%] [0/4]'; let document = await loadContent(content2level); let editor = await vscode.window.showTextDocument(document); - let registration = vscode.commands.registerTextEditorCommand('org.updateSummary', checkboxes.OrgUpdateSummary); moveAndSelect(editor, 9, 5); await vscode.commands.executeCommand('org.updateSummary'); let actual = document.lineAt(9).text; assert.equal(actual, expected); - registration.dispose(); }); test('Ticking checkbox updates parent', async () => { let expected = ' - [-] toggling child checkbox [25%]'; let document = await loadContent(content2level); let editor = await vscode.window.showTextDocument(document); - let registration = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', checkboxes.OrgToggleCheckbox); moveAndSelect(editor, 14, 14); await vscode.commands.executeCommand('org.toggleCheckbox'); let actual = document.lineAt(12).text; assert.equal(actual, expected); - registration.dispose(); }); test('Ticking parent checkbox ticks all children', async () => { const expected = [ @@ -119,38 +115,32 @@ suite('Checkboxes', () => { const lineNo = [13, 14, 15, 16]; let document = await loadContent(content2level); let editor = await vscode.window.showTextDocument(document); - let registration = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', checkboxes.OrgToggleCheckbox); moveAndSelect(editor, 12, 14); await vscode.commands.executeCommand('org.toggleCheckbox'); for (let i: number = 0; i < expected.length; i++) { let actual = document.lineAt(lineNo[i]).text; assert.equal(actual, expected[i]); } - registration.dispose(); }); test('Unticking last ticked child clears parent checkbox', async () => { let expected = ' - [ ] call people [0/3]'; let document = await loadContent(content2level); let editor = await vscode.window.showTextDocument(document); - let registration = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', checkboxes.OrgToggleCheckbox); moveAndSelect(editor, 3, 14); await vscode.commands.executeCommand('org.toggleCheckbox'); let actual = document.lineAt(1).text; assert.equal(actual, expected); - registration.dispose(); }); test('Ticking all children ticks parent checkbox', async () => { let expected = ' - [x] toggling parent checkbox [3/3]'; let document = await loadContent(content2level); let editor = await vscode.window.showTextDocument(document); - let registration = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', checkboxes.OrgToggleCheckbox); moveAndSelect(editor, 18, 5); await vscode.commands.executeCommand('org.toggleCheckbox'); moveAndSelect(editor, 20, 5); await vscode.commands.executeCommand('org.toggleCheckbox'); let actual = document.lineAt(17).text; assert.equal(actual, expected); - registration.dispose(); }); test('Ticking parent checkbox ticks all children (3 level)', async () => { const expected = [ @@ -160,13 +150,11 @@ suite('Checkboxes', () => { const lineNo = [3, 9]; let document = await loadContent(content3level); let editor = await vscode.window.showTextDocument(document); - let registration = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', checkboxes.OrgToggleCheckbox); moveAndSelect(editor, 1, 7); await vscode.commands.executeCommand('org.toggleCheckbox'); for (let i: number = 0; i < expected.length; i++) { let actual = document.lineAt(lineNo[i]).text; assert.equal(actual, expected[i]); } - registration.dispose(); }); }); From 9a9c3b447bcbfd4f9fe2046d4770873e3b323d2e Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Thu, 11 Oct 2018 10:24:19 -0500 Subject: [PATCH 24/27] Added test for ticking parent and unticking one child --- test/checkboxes.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/checkboxes.test.ts b/test/checkboxes.test.ts index 3322280..defff73 100644 --- a/test/checkboxes.test.ts +++ b/test/checkboxes.test.ts @@ -157,4 +157,15 @@ suite('Checkboxes', () => { assert.equal(actual, expected[i]); } }); + test('Unticking child checkbox makes parent untetermined (3 level)', async () => { + const expected = ' - [-] dessert [2/3]'; + let document = await loadContent(content3level); + let editor = await vscode.window.showTextDocument(document); + moveAndSelect(editor, 1, 7); + await vscode.commands.executeCommand('org.toggleCheckbox'); + moveAndSelect(editor, 8, 7); + await vscode.commands.executeCommand('org.toggleCheckbox'); + let actual = document.lineAt(7).text; + assert.equal(actual, expected); + }); }); From c608c7e045291fbf5b33b2e2e5df7105beee21a7 Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Thu, 11 Oct 2018 10:49:54 -0500 Subject: [PATCH 25/27] Get document tab witdh from workspace configuration --- src/checkboxes.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/checkboxes.ts b/src/checkboxes.ts index abb1ca1..3c56f12 100644 --- a/src/checkboxes.ts +++ b/src/checkboxes.ts @@ -1,6 +1,6 @@ 'use strict'; -import { window, TextLine, Range, TextEditor, TextEditorEdit } from 'vscode'; +import { window, workspace, TextLine, Range, TextEditor, TextEditorEdit } from 'vscode'; // Checkbox is represented by exactly one symbol between square brackets. Symbol indicates status: '-' undetermined, 'x' or 'X' checked, ' ' unchecked. const checkboxRegex = /\[([-xX ])\]/; @@ -9,6 +9,7 @@ const summaryRegex = /\[(\d*\/\d*)\]/; // Percentage is a cookie indicating the percentage of ticked checkboxes in the child list relative to the total number of checkboxes in the list. const percentRegex = /\[(\d*)%\]/; const indentRegex = /^(\s*)\S/; +let orgTabSize: number = 4; export function OrgTabsToSpaces(tabs: string, tabSize: number = 4): number { if (!tabs) { @@ -30,6 +31,7 @@ export function OrgToggleCheckbox(editor: TextEditor, edit: TextEditorEdit) { let line = doc.lineAt(editor.selection.active.line); let checkbox = orgFindCookie(checkboxRegex, line); if (checkbox) { + orgTabSize = workspace.getConfiguration('editor', doc.uri).get('tabSize'); let text = doc.getText(checkbox).toLowerCase(); let delta = orgCascadeCheckbox(edit, checkbox, line, text == 'x' ? ' ' : 'x'); let parent = orgFindParent(editor, line); @@ -45,6 +47,7 @@ export function OrgToggleCheckbox(editor: TextEditor, edit: TextEditorEdit) { export function OrgUpdateSummary(editor: TextEditor, edit: TextEditorEdit) { let doc = editor.document; let line = doc.lineAt(editor.selection.active.line); + orgTabSize = workspace.getConfiguration('editor', doc.uri).get('tabSize'); orgUpdateParent(editor, edit, line, 0); } @@ -73,7 +76,7 @@ function orgGetTriState(checked, total: number): string { function orgGetIndent(line: TextLine): number { let match = indentRegex.exec(line.text); if (match) { - return OrgTabsToSpaces(match[1]); + return OrgTabsToSpaces(match[1], orgTabSize); } return 0; } From e606ced3dcc01809b990a71752d028a3aeb3e29f Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Thu, 11 Oct 2018 10:50:26 -0500 Subject: [PATCH 26/27] Added key bindings for summary update and checkbox toggle --- package.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/package.json b/package.json index b2a2067..facccc0 100644 --- a/package.json +++ b/package.json @@ -245,6 +245,16 @@ "command": "org.literal", "key": "ctrl+alt+o l", "when": "editorLangId == 'org'" + }, + { + "command": "org.updateSummary", + "key": "ctrl+alt+o #", + "when": "editorLangId == 'org'" + }, + { + "command": "org.toggleCheckbox", + "key": "ctrl+alt+o x", + "when": "editorLangId == 'org'" } ] }, From 729520bc98b1897de4d3d926c847243fa41c11e0 Mon Sep 17 00:00:00 2001 From: Alexei Boukirev Date: Mon, 15 Oct 2018 08:43:32 -0500 Subject: [PATCH 27/27] Comply with camelCase standard for exported functions --- src/checkboxes.ts | 8 ++++---- src/extension.ts | 4 ++-- test/checkboxes.test.ts | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/checkboxes.ts b/src/checkboxes.ts index 3c56f12..e1fc9ae 100644 --- a/src/checkboxes.ts +++ b/src/checkboxes.ts @@ -11,7 +11,7 @@ const percentRegex = /\[(\d*)%\]/; const indentRegex = /^(\s*)\S/; let orgTabSize: number = 4; -export function OrgTabsToSpaces(tabs: string, tabSize: number = 4): number { +export function orgTabsToSpaces(tabs: string, tabSize: number = 4): number { if (!tabs) { return 0; } @@ -26,7 +26,7 @@ export function OrgTabsToSpaces(tabs: string, tabSize: number = 4): number { return off; } -export function OrgToggleCheckbox(editor: TextEditor, edit: TextEditorEdit) { +export function orgToggleCheckbox(editor: TextEditor, edit: TextEditorEdit) { let doc = editor.document; let line = doc.lineAt(editor.selection.active.line); let checkbox = orgFindCookie(checkboxRegex, line); @@ -44,7 +44,7 @@ export function OrgToggleCheckbox(editor: TextEditor, edit: TextEditorEdit) { } } -export function OrgUpdateSummary(editor: TextEditor, edit: TextEditorEdit) { +export function orgUpdateSummary(editor: TextEditor, edit: TextEditorEdit) { let doc = editor.document; let line = doc.lineAt(editor.selection.active.line); orgTabSize = workspace.getConfiguration('editor', doc.uri).get('tabSize'); @@ -76,7 +76,7 @@ function orgGetTriState(checked, total: number): string { function orgGetIndent(line: TextLine): number { let match = indentRegex.exec(line.text); if (match) { - return OrgTabsToSpaces(match[1], orgTabSize); + return orgTabsToSpaces(match[1], orgTabSize); } return 0; } diff --git a/src/extension.ts b/src/extension.ts index 0f277c0..b186a5a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -36,8 +36,8 @@ export function activate(context: vscode.ExtensionContext) { const verboseCmd = vscode.commands.registerTextEditorCommand('org.verbose', MarkupFunctions.verbose); const literalCmd = vscode.commands.registerTextEditorCommand('org.literal', MarkupFunctions.literal); const butterflyCmd = vscode.commands.registerTextEditorCommand('org.butterfly', PascuaneseFunctions.butterfly); - const updateSummaryCmd = vscode.commands.registerTextEditorCommand('org.updateSummary', Checkboxes.OrgUpdateSummary); - const toggleCheckboxCmd = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', Checkboxes.OrgToggleCheckbox); + const updateSummaryCmd = vscode.commands.registerTextEditorCommand('org.updateSummary', Checkboxes.orgUpdateSummary); + const toggleCheckboxCmd = vscode.commands.registerTextEditorCommand('org.toggleCheckbox', Checkboxes.orgToggleCheckbox); context.subscriptions.push(insertHeadingRespectContentCmd); context.subscriptions.push(insertChildCmd); diff --git a/test/checkboxes.test.ts b/test/checkboxes.test.ts index defff73..f9dec07 100644 --- a/test/checkboxes.test.ts +++ b/test/checkboxes.test.ts @@ -82,8 +82,8 @@ suite('Checkboxes', () => { ]; for (let i: number = 0; i < cases.length; i++) { - assert.equal(checkboxes.OrgTabsToSpaces(cases[i], 4), expected4[i]); - assert.equal(checkboxes.OrgTabsToSpaces(cases[i], 8), expected8[i]); + assert.equal(checkboxes.orgTabsToSpaces(cases[i], 4), expected4[i]); + assert.equal(checkboxes.orgTabsToSpaces(cases[i], 8), expected8[i]); } done(); });