From aa18457d7646ec6d916a4bc0dc564aa58e12a586 Mon Sep 17 00:00:00 2001 From: Matthew Crumley Date: Sun, 22 Oct 2017 17:16:07 -0400 Subject: [PATCH 01/16] Add autocomplete provider --- lib/language-todotxt.js | 5 ++ lib/todotxt-autocomplete-provider.js | 121 +++++++++++++++++++++++++++ package.json | 9 +- spec/autocomplete-spec.js | 78 +++++++++++++++++ 4 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 lib/todotxt-autocomplete-provider.js create mode 100644 spec/autocomplete-spec.js diff --git a/lib/language-todotxt.js b/lib/language-todotxt.js index bc7ef30..cc99968 100644 --- a/lib/language-todotxt.js +++ b/lib/language-todotxt.js @@ -28,6 +28,7 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import { CompositeDisposable } from 'atom' +import autocompleteProvider from './todotxt-autocomplete-provider' export default { @@ -102,5 +103,9 @@ export default { te.insertText(line) te.moveToBeginningOfLine() } + }, + + getAutocompleteProvider() { + return autocompleteProvider } } diff --git a/lib/todotxt-autocomplete-provider.js b/lib/todotxt-autocomplete-provider.js new file mode 100644 index 0000000..00063dd --- /dev/null +++ b/lib/todotxt-autocomplete-provider.js @@ -0,0 +1,121 @@ +'use babel' + +import { Point, Range } from 'atom' + +export default { + selector: '.text.todotxt', + disableForSelector: '.text.todotxt .comment', + + inclusionPriority: 1, + excludeLowerPriority: false, + + suggestionPriority: 2, + + filterSuggestions: false, + + getSuggestions({ editor, bufferPosition, activatedManually }) { + let prefix = getPrefix(editor, bufferPosition) + + var suggestions + if (prefix.length === 0 && !activatedManually) { + suggestions = [] + } else { + suggestions = findSuggestions(editor, prefix, getPrefixRange(bufferPosition, prefix)) + } + + let existing = getCurrentTags(editor, prefix, bufferPosition) + return sortSuggestions(filterSuggestions(suggestions, existing)) + } +} + +function findProjects(editor, prefix, prefixRange) { + return findTags(editor, prefix, 'project', prefixRange) +} + +function findContexts(editor, prefix, prefixRange) { + return findTags(editor, prefix, 'context', prefixRange) +} + +function findSuggestions(editor, prefix, prefixRange) { + if (prefix.startsWith('+')) { + return findProjects(editor, prefix, prefixRange) + } else if (prefix.startsWith('@')) { + return findContexts(editor, prefix, prefixRange) + } else if (prefix === '') { + return findProjects(editor, '').concat(findContexts(editor, '')) + } else { + return [] + } +} + +// Find +project or @context tags (excluding any that intersect with excludeRange) with a given prefix +function findTags(editor, prefix, type, excludeRange) { + let completions = [] + let index = {} + scanForTags(editor, type, editor.getBuffer().getRange(), ({ match, range }) => { + if (excludeRange && range.intersectsWith(excludeRange)) { + return + } + + let name = match[2] + let tag = `${match[1]}${name}` + if (!index.hasOwnProperty(name) && tag.startsWith(prefix)) { + completions.push({ + text: tag, + replacementPrefix: prefix, + type: 'tag', + iconHTML: `${type.charAt(0)}`, + rightLabel: type == 'context' ? 'Context' : 'Project' + }) + index[name] = true + } + }) + return completions +} + +// Scan a range for +project or @context tags, calling callback for each match +function scanForTags(editor, type, range, callback) { + let regex = type == 'context' ? /(?:^|\s)(@)(\S+)/g : /(?:^|\s)([+])(\S+)/g + editor.scanInBufferRange(regex, range, callback) +} + +// Get the +project or @context tags on the current line, except for the current prefix +function getCurrentTags(editor, prefix, bufferPosition) { + let existing = {} + let prefixRange = getPrefixRange(bufferPosition, prefix) + let wholeRow = editor.getBuffer().rangeForRow(bufferPosition.row) + + function addTag({ match, range }) { + if (!range.intersectsWith(prefixRange)) { + existing[`${match[1]}${match[2]}`] = true + } + } + + scanForTags(editor, 'project', wholeRow, addTag) + scanForTags(editor, 'context', wholeRow, addTag) + + return existing +} + +function filterSuggestions(completions, existing) { + return completions.filter(completion => !existing.hasOwnProperty(completion.text)) +} + +function sortSuggestions(completions) { + return completions.sort((a, b) => { + let aText = a.text.substring(1).toLowerCase() + let bText = b.text.substring(1).toLowerCase() + return aText < bText ? -1 : aText > bText ? 1 : 0 + }) +} + +function getPrefixRange(bufferPosition, prefix) { + return new Range(new Point(bufferPosition.row, bufferPosition.column - prefix.length), bufferPosition) +} + +function getPrefix(editor, bufferPosition) { + let regex = /(?:^|\s)([+@]?\S*)$/ + let line = editor.getTextInBufferRange([[bufferPosition.row, 0], bufferPosition]) + let match = line.match(regex) + return match ? match[1] : '' +} diff --git a/package.json b/package.json index faff30a..25b48d9 100644 --- a/package.json +++ b/package.json @@ -8,5 +8,12 @@ "engines": { "atom": ">=1.0.0 <2.0.0" }, - "dependencies": {} + "dependencies": {}, + "providedServices": { + "autocomplete.provider": { + "versions": { + "2.0.0": "getAutocompleteProvider" + } + } + } } diff --git a/spec/autocomplete-spec.js b/spec/autocomplete-spec.js new file mode 100644 index 0000000..b7f0656 --- /dev/null +++ b/spec/autocomplete-spec.js @@ -0,0 +1,78 @@ +// autocomplete-spec.js -- Test the autocomplete provider for the project +// +// Copyright (c) 2017, Evan Prodromou +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of the organization nor the +// names of its contributors may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"use babel"; + +let suggestionsForPrefix = (provider, editor, prefix, options) => { + let bufferPosition = editor.getCursorBufferPosition() + let scopeDescriptor = editor.getLastCursor().getScopeDescriptor() + let suggestions = provider.getSuggestions({editor, bufferPosition, prefix, scopeDescriptor}) + if (options && options.raw) { + return suggestions + } else { + if (suggestions) { + return (suggestions.map((sug) => sug.text)) + } else { + return [] + } + } +} + +describe("todo.txt autocompletion", () => { + var provider; + + const workspaceElement = () => { + return atom.views.getView(atom.workspace) + }; + + beforeEach(() => { + waitsForPromise(() => { + return atom.workspace.open("todo.txt"); + }); + + waitsForPromise(() => { + return atom.packages.activatePackage("language-todotxt").then(package => { + provider = package.mainModule.getAutocompleteProvider(); + }); + }); + }); + + it("finds all suggestions with no prefix", () => { + let te = atom.workspace.getActiveTextEditor(); + te.moveDown(9); + let text = getCurrentLine(te); + expect(text).toEqual(""); + expect(suggestionsForPrefix(provider, te, "")).toEqual([ + "@computer", + "+GarageSale", + "@GroceryStore", + "+LanguageTodotxt", + "@phone" + ]); + }); + +}); From aa37f3bf40773d5af616235b38cf8bce2ac60b77 Mon Sep 17 00:00:00 2001 From: Matthew Crumley Date: Sun, 22 Oct 2017 17:27:37 -0400 Subject: [PATCH 02/16] Fix test --- spec/autocomplete-spec.js | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/spec/autocomplete-spec.js b/spec/autocomplete-spec.js index b7f0656..7f9814f 100644 --- a/spec/autocomplete-spec.js +++ b/spec/autocomplete-spec.js @@ -43,36 +43,41 @@ let suggestionsForPrefix = (provider, editor, prefix, options) => { } describe("todo.txt autocompletion", () => { - var provider; + var provider const workspaceElement = () => { return atom.views.getView(atom.workspace) - }; + } + + const getCurrentLine = (te) => { + let pt = te.getCursorBufferPosition() + return te.lineTextForBufferRow(pt.row) + } beforeEach(() => { waitsForPromise(() => { - return atom.workspace.open("todo.txt"); - }); + return atom.workspace.open("todo.txt") + }) waitsForPromise(() => { return atom.packages.activatePackage("language-todotxt").then(package => { - provider = package.mainModule.getAutocompleteProvider(); - }); - }); - }); + provider = package.mainModule.getAutocompleteProvider() + }) + }) + }) it("finds all suggestions with no prefix", () => { - let te = atom.workspace.getActiveTextEditor(); - te.moveDown(9); - let text = getCurrentLine(te); - expect(text).toEqual(""); + let te = atom.workspace.getActiveTextEditor() + te.moveDown(9) + let text = getCurrentLine(te) + expect(text).toEqual("") expect(suggestionsForPrefix(provider, te, "")).toEqual([ "@computer", "+GarageSale", "@GroceryStore", "+LanguageTodotxt", "@phone" - ]); - }); + ]) + }) -}); +}) From 207db3d6634cd8ac60b659cd41e6cbb1bcda7139 Mon Sep 17 00:00:00 2001 From: Matthew Crumley Date: Sun, 22 Oct 2017 17:35:48 -0400 Subject: [PATCH 03/16] Fix tests --- spec/autocomplete-spec.js | 41 +++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/spec/autocomplete-spec.js b/spec/autocomplete-spec.js index 7f9814f..97a98b2 100644 --- a/spec/autocomplete-spec.js +++ b/spec/autocomplete-spec.js @@ -27,21 +27,6 @@ "use babel"; -let suggestionsForPrefix = (provider, editor, prefix, options) => { - let bufferPosition = editor.getCursorBufferPosition() - let scopeDescriptor = editor.getLastCursor().getScopeDescriptor() - let suggestions = provider.getSuggestions({editor, bufferPosition, prefix, scopeDescriptor}) - if (options && options.raw) { - return suggestions - } else { - if (suggestions) { - return (suggestions.map((sug) => sug.text)) - } else { - return [] - } - } -} - describe("todo.txt autocompletion", () => { var provider @@ -54,6 +39,21 @@ describe("todo.txt autocompletion", () => { return te.lineTextForBufferRow(pt.row) } + const suggestionsForPrefix = (provider, editor, prefix, options) => { + let bufferPosition = editor.getCursorBufferPosition() + let scopeDescriptor = editor.getLastCursor().getScopeDescriptor() + let suggestions = provider.getSuggestions({editor, bufferPosition, prefix, scopeDescriptor, activatedManually: (options && options.activatedManually)}) + if (options && options.raw) { + return suggestions + } else { + if (suggestions) { + return (suggestions.map((sug) => sug.text)) + } else { + return [] + } + } + } + beforeEach(() => { waitsForPromise(() => { return atom.workspace.open("todo.txt") @@ -66,12 +66,12 @@ describe("todo.txt autocompletion", () => { }) }) - it("finds all suggestions with no prefix", () => { + it("finds all suggestions when activated manually with no prefix", () => { let te = atom.workspace.getActiveTextEditor() te.moveDown(9) let text = getCurrentLine(te) expect(text).toEqual("") - expect(suggestionsForPrefix(provider, te, "")).toEqual([ + expect(suggestionsForPrefix(provider, te, "", { activatedManually: true })).toEqual([ "@computer", "+GarageSale", "@GroceryStore", @@ -80,4 +80,11 @@ describe("todo.txt autocompletion", () => { ]) }) + it("finds no suggestions with no prefix", () => { + let te = atom.workspace.getActiveTextEditor() + te.moveDown(9) + let text = getCurrentLine(te) + expect(text).toEqual("") + expect(suggestionsForPrefix(provider, te, "", { activatedManually: true })).toEqual([]) + }) }) From c899305a6d5c9cef6ef7f229dc55b643e2cec1e3 Mon Sep 17 00:00:00 2001 From: Matthew Crumley Date: Sun, 22 Oct 2017 17:39:05 -0400 Subject: [PATCH 04/16] Disable activatedManually --- spec/autocomplete-spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/autocomplete-spec.js b/spec/autocomplete-spec.js index 97a98b2..fd1d364 100644 --- a/spec/autocomplete-spec.js +++ b/spec/autocomplete-spec.js @@ -85,6 +85,6 @@ describe("todo.txt autocompletion", () => { te.moveDown(9) let text = getCurrentLine(te) expect(text).toEqual("") - expect(suggestionsForPrefix(provider, te, "", { activatedManually: true })).toEqual([]) + expect(suggestionsForPrefix(provider, te, "", { activatedManually: false })).toEqual([]) }) }) From 2312f85b0fa8847f505973eb0d6dabd39b3b1210 Mon Sep 17 00:00:00 2001 From: Matthew Crumley Date: Sun, 22 Oct 2017 17:46:58 -0400 Subject: [PATCH 05/16] More tests --- spec/autocomplete-spec.js | 70 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/spec/autocomplete-spec.js b/spec/autocomplete-spec.js index fd1d364..78f203e 100644 --- a/spec/autocomplete-spec.js +++ b/spec/autocomplete-spec.js @@ -87,4 +87,74 @@ describe("todo.txt autocompletion", () => { expect(text).toEqual("") expect(suggestionsForPrefix(provider, te, "", { activatedManually: false })).toEqual([]) }) + + it("labels suggestions correctly", () => { + let te = atom.workspace.getActiveTextEditor() + te.moveDown(9) + let text = getCurrentLine(te) + expect(text).toEqual("") + let suggestions = suggestionsForPrefix(provider, te, "", { activatedManually: true, raw: true }) + expect(suggestions[0].text).toEqual("@computer") + expect(suggestions[0].type).toEqual("tag") + expect(suggestions[0].rightLabel).toEqual("Context") + + expect(suggestions[1].text).toEqual("+GarageSale") + expect(suggestions[1].type).toEqual("tag") + expect(suggestions[1].rightLabel).toEqual("Project") + + expect(suggestions[2].text).toEqual("@GroceryStore") + expect(suggestions[2].type).toEqual("tag") + expect(suggestions[2].rightLabel).toEqual("Context") + + expect(suggestions[3].text).toEqual("+LanguageTodotxt") + expect(suggestions[3].type).toEqual("tag") + expect(suggestions[3].rightLabel).toEqual("Project") + + expect(suggestions[4].text).toEqual("@phone") + expect(suggestions[4].type).toEqual("tag") + expect(suggestions[4].rightLabel).toEqual("Context") + }) + + it("finds all projects with + prefix", () => { + let te = atom.workspace.getActiveTextEditor() + te.moveDown(9) + let text = getCurrentLine(te) + expect(text).toEqual("") + expect(suggestionsForPrefix(provider, te, "+")).toEqual([ + "+GarageSale", + "+LanguageTodotxt" + ]) + }) + + it("finds all contexts with @ prefix", () => { + let te = atom.workspace.getActiveTextEditor() + te.moveDown(9) + let text = getCurrentLine(te) + expect(text).toEqual("") + expect(suggestionsForPrefix(provider, te, "@")).toEqual([ + "@computer", + "@GroceryStore", + "@phone" + ]) + }) + + it("finds only matching contexts with @G prefix", () => { + let te = atom.workspace.getActiveTextEditor() + te.moveDown(9) + let text = getCurrentLine(te) + expect(text).toEqual("") + expect(suggestionsForPrefix(provider, te, "@G")).toEqual([ + "@GroceryStore" + ]) + }) + + it("finds only matching projects with +La prefix", () => { + let te = atom.workspace.getActiveTextEditor() + te.moveDown(9) + let text = getCurrentLine(te) + expect(text).toEqual("") + expect(suggestionsForPrefix(provider, te, "+La")).toEqual([ + "@LanguageTodotxt" + ]) + }) }) From 8789a95b8b5e088afb9052d949c440f793ec9a3e Mon Sep 17 00:00:00 2001 From: Matthew Crumley Date: Sun, 22 Oct 2017 17:57:19 -0400 Subject: [PATCH 06/16] Add text instead of passing the prefix --- spec/autocomplete-spec.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/spec/autocomplete-spec.js b/spec/autocomplete-spec.js index 78f203e..5b566b9 100644 --- a/spec/autocomplete-spec.js +++ b/spec/autocomplete-spec.js @@ -39,10 +39,9 @@ describe("todo.txt autocompletion", () => { return te.lineTextForBufferRow(pt.row) } - const suggestionsForPrefix = (provider, editor, prefix, options) => { + const suggestionsForPrefix = (provider, editor, options) => { let bufferPosition = editor.getCursorBufferPosition() - let scopeDescriptor = editor.getLastCursor().getScopeDescriptor() - let suggestions = provider.getSuggestions({editor, bufferPosition, prefix, scopeDescriptor, activatedManually: (options && options.activatedManually)}) + let suggestions = provider.getSuggestions({editor, bufferPosition, activatedManually: (options && options.activatedManually)}) if (options && options.raw) { return suggestions } else { @@ -71,7 +70,7 @@ describe("todo.txt autocompletion", () => { te.moveDown(9) let text = getCurrentLine(te) expect(text).toEqual("") - expect(suggestionsForPrefix(provider, te, "", { activatedManually: true })).toEqual([ + expect(suggestionsForPrefix(provider, te, { activatedManually: true })).toEqual([ "@computer", "+GarageSale", "@GroceryStore", @@ -85,7 +84,7 @@ describe("todo.txt autocompletion", () => { te.moveDown(9) let text = getCurrentLine(te) expect(text).toEqual("") - expect(suggestionsForPrefix(provider, te, "", { activatedManually: false })).toEqual([]) + expect(suggestionsForPrefix(provider, te, { activatedManually: false })).toEqual([]) }) it("labels suggestions correctly", () => { @@ -93,7 +92,7 @@ describe("todo.txt autocompletion", () => { te.moveDown(9) let text = getCurrentLine(te) expect(text).toEqual("") - let suggestions = suggestionsForPrefix(provider, te, "", { activatedManually: true, raw: true }) + let suggestions = suggestionsForPrefix(provider, te, { activatedManually: true, raw: true }) expect(suggestions[0].text).toEqual("@computer") expect(suggestions[0].type).toEqual("tag") expect(suggestions[0].rightLabel).toEqual("Context") @@ -120,7 +119,8 @@ describe("todo.txt autocompletion", () => { te.moveDown(9) let text = getCurrentLine(te) expect(text).toEqual("") - expect(suggestionsForPrefix(provider, te, "+")).toEqual([ + editor.insertText("+") + expect(suggestionsForPrefix(provider, te)).toEqual([ "+GarageSale", "+LanguageTodotxt" ]) @@ -131,7 +131,8 @@ describe("todo.txt autocompletion", () => { te.moveDown(9) let text = getCurrentLine(te) expect(text).toEqual("") - expect(suggestionsForPrefix(provider, te, "@")).toEqual([ + editor.insertText("@") + expect(suggestionsForPrefix(provider, te)).toEqual([ "@computer", "@GroceryStore", "@phone" @@ -143,7 +144,8 @@ describe("todo.txt autocompletion", () => { te.moveDown(9) let text = getCurrentLine(te) expect(text).toEqual("") - expect(suggestionsForPrefix(provider, te, "@G")).toEqual([ + editor.insertText("@G") + expect(suggestionsForPrefix(provider, te)).toEqual([ "@GroceryStore" ]) }) @@ -153,7 +155,8 @@ describe("todo.txt autocompletion", () => { te.moveDown(9) let text = getCurrentLine(te) expect(text).toEqual("") - expect(suggestionsForPrefix(provider, te, "+La")).toEqual([ + editor.insertText("+La") + expect(suggestionsForPrefix(provider, te)).toEqual([ "@LanguageTodotxt" ]) }) From 303702f3c25b7d7e60c09412f3fc288d922ed32c Mon Sep 17 00:00:00 2001 From: Matthew Crumley Date: Sun, 22 Oct 2017 18:01:34 -0400 Subject: [PATCH 07/16] editor -> te --- spec/autocomplete-spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/autocomplete-spec.js b/spec/autocomplete-spec.js index 5b566b9..7388ab9 100644 --- a/spec/autocomplete-spec.js +++ b/spec/autocomplete-spec.js @@ -119,7 +119,7 @@ describe("todo.txt autocompletion", () => { te.moveDown(9) let text = getCurrentLine(te) expect(text).toEqual("") - editor.insertText("+") + te.insertText("+") expect(suggestionsForPrefix(provider, te)).toEqual([ "+GarageSale", "+LanguageTodotxt" @@ -131,7 +131,7 @@ describe("todo.txt autocompletion", () => { te.moveDown(9) let text = getCurrentLine(te) expect(text).toEqual("") - editor.insertText("@") + te.insertText("@") expect(suggestionsForPrefix(provider, te)).toEqual([ "@computer", "@GroceryStore", @@ -144,7 +144,7 @@ describe("todo.txt autocompletion", () => { te.moveDown(9) let text = getCurrentLine(te) expect(text).toEqual("") - editor.insertText("@G") + te.insertText("@G") expect(suggestionsForPrefix(provider, te)).toEqual([ "@GroceryStore" ]) @@ -155,7 +155,7 @@ describe("todo.txt autocompletion", () => { te.moveDown(9) let text = getCurrentLine(te) expect(text).toEqual("") - editor.insertText("+La") + te.insertText("+La") expect(suggestionsForPrefix(provider, te)).toEqual([ "@LanguageTodotxt" ]) From 92fdebdc50a33480999985b54d5d55ba8985e356 Mon Sep 17 00:00:00 2001 From: Matthew Crumley Date: Sun, 22 Oct 2017 18:04:56 -0400 Subject: [PATCH 08/16] +LanguageTodotxt not @LanguageTodotxt --- spec/autocomplete-spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/autocomplete-spec.js b/spec/autocomplete-spec.js index 7388ab9..d6b070c 100644 --- a/spec/autocomplete-spec.js +++ b/spec/autocomplete-spec.js @@ -157,7 +157,7 @@ describe("todo.txt autocompletion", () => { expect(text).toEqual("") te.insertText("+La") expect(suggestionsForPrefix(provider, te)).toEqual([ - "@LanguageTodotxt" + "+LanguageTodotxt" ]) }) }) From 9188faa5ad94ba3b5fa68adfad0cf7a395c6829f Mon Sep 17 00:00:00 2001 From: Matthew Crumley Date: Sun, 22 Oct 2017 18:11:46 -0400 Subject: [PATCH 09/16] Add tests for current line filter --- spec/autocomplete-spec.js | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/spec/autocomplete-spec.js b/spec/autocomplete-spec.js index d6b070c..fc4922f 100644 --- a/spec/autocomplete-spec.js +++ b/spec/autocomplete-spec.js @@ -160,4 +160,48 @@ describe("todo.txt autocompletion", () => { "+LanguageTodotxt" ]) }) + + it("finds does not suggest tags that are already on the current line", () => { + let te = atom.workspace.getActiveTextEditor() + te.moveDown(1) + let text = getCurrentLine(te) + expect(text).toEqual("(B) Schedule Goodwill pickup +GarageSale @phone") + te.moveToEndOfLine() + te.insertText(" ") + expect(suggestionsForPrefix(provider, te, { activatedManually: true })).toEqual([ + "@computer", + "@GroceryStore", + "+LanguageTodotxt" + ]) + }) + + it("finds does not suggest tags that are already on the current line", () => { + let te = atom.workspace.getActiveTextEditor() + te.moveDown(1) + let text = getCurrentLine(te) + expect(text).toEqual("(B) Schedule Goodwill pickup +GarageSale @phone") + te.moveToEndOfLine() + te.insertText(" ") + expect(suggestionsForPrefix(provider, te, { activatedManually: true })).toEqual([ + "@computer", + "@GroceryStore", + "+LanguageTodotxt" + ]) + }) + + it("finds does not filter out non-tags on the current line", () => { + let te = atom.workspace.getActiveTextEditor() + let text = getCurrentLine(te) + expect(text).toEqual("(A) Thank Mom for the meatballs @phone") + te.moveToEndOfLine() + te.insertText(" @example.com") + te.moveDown(4) + text = getCurrentLine(te) + expect(text).toEqual("(C) email user@example.com @computer") + te.moveToEndOfLine() + te.insertText(" @e") + expect(suggestionsForPrefix(provider, te)).toEqual([ + "@example.com" + ]) + }) }) From 55ea3c949b25277889dcb06496d2c05c9bbe0138 Mon Sep 17 00:00:00 2001 From: Matthew Crumley Date: Sun, 22 Oct 2017 19:08:07 -0400 Subject: [PATCH 10/16] Test for Run todotxt:done command in a transaction --- spec/done-command-spec.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/spec/done-command-spec.js b/spec/done-command-spec.js index ed67941..b819127 100644 --- a/spec/done-command-spec.js +++ b/spec/done-command-spec.js @@ -119,4 +119,25 @@ describe("todo.txt done command", () => { expect(text).toEqual("x 2017-10-19 todotxt:done on a line that is already done +LanguageTodotxt"); }); + it("makes edits in a single transaction", () => { + + const getCurrentLine = (te) => { + let pt = te.getCursorBufferPosition(); + return te.lineTextForBufferRow(pt.row); + }; + + let te = atom.workspace.getActiveTextEditor(); + te.moveDown(5); + let text = getCurrentLine(te); + expect(text).toEqual("(A) 2017-10-17 Implement the todotxt:done command +LanguageTodotxt"); + atom.commands.dispatch(workspaceElement(), 'todotxt:done'); + text = getCurrentLine(te); + let dt = new Date(); + let now = `${dt.getFullYear()}-${(dt.getMonth() < 8) ? '0' : ''}${dt.getMonth() + 1}-${dt.getDate() < 10 ? '0' : ''}${dt.getDate()}`; + expect(text).toEqual(`x ${now} 2017-10-17 Implement the todotxt:done command +LanguageTodotxt pri:A`); + + te.undo(); + expect(text).toEqual("(A) 2017-10-17 Implement the todotxt:done command +LanguageTodotxt"); + }); + }); From 655b9df83d33288bdc6949c80fa0bbee608997f3 Mon Sep 17 00:00:00 2001 From: Matthew Crumley Date: Sun, 22 Oct 2017 19:09:34 -0400 Subject: [PATCH 11/16] Run todotxt:done command in a transaction --- lib/language-todotxt.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/language-todotxt.js b/lib/language-todotxt.js index cc99968..b7e9624 100644 --- a/lib/language-todotxt.js +++ b/lib/language-todotxt.js @@ -73,10 +73,12 @@ export default { } let now = this.dateNow() line = `x ${now} ${line}` - te.moveToBeginningOfLine() - te.deleteToEndOfLine() - te.insertText(line) - te.moveToBeginningOfLine() + te.getBuffer().transact(() => { + te.moveToBeginningOfLine() + te.deleteToEndOfLine() + te.insertText(line) + te.moveToBeginningOfLine() + }) } }, From b87b8841bd966b27d65983feaf7c72eadd120fad Mon Sep 17 00:00:00 2001 From: Matthew Crumley Date: Sun, 22 Oct 2017 19:22:56 -0400 Subject: [PATCH 12/16] Update test --- lib/language-todotxt.js | 4 ++-- spec/done-command-spec.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/language-todotxt.js b/lib/language-todotxt.js index b7e9624..0403572 100644 --- a/lib/language-todotxt.js +++ b/lib/language-todotxt.js @@ -73,12 +73,12 @@ export default { } let now = this.dateNow() line = `x ${now} ${line}` - te.getBuffer().transact(() => { + //te.getBuffer().transact(() => { te.moveToBeginningOfLine() te.deleteToEndOfLine() te.insertText(line) te.moveToBeginningOfLine() - }) + //}) } }, diff --git a/spec/done-command-spec.js b/spec/done-command-spec.js index b819127..5ae596f 100644 --- a/spec/done-command-spec.js +++ b/spec/done-command-spec.js @@ -136,7 +136,7 @@ describe("todo.txt done command", () => { let now = `${dt.getFullYear()}-${(dt.getMonth() < 8) ? '0' : ''}${dt.getMonth() + 1}-${dt.getDate() < 10 ? '0' : ''}${dt.getDate()}`; expect(text).toEqual(`x ${now} 2017-10-17 Implement the todotxt:done command +LanguageTodotxt pri:A`); - te.undo(); + atom.commands.dispatch(workspaceElement(), 'core:undo'); expect(text).toEqual("(A) 2017-10-17 Implement the todotxt:done command +LanguageTodotxt"); }); From 0ca0eb3794b7d313cd14e85ed5b07a68017f97d9 Mon Sep 17 00:00:00 2001 From: Matthew Crumley Date: Sun, 22 Oct 2017 19:40:17 -0400 Subject: [PATCH 13/16] Fix test --- spec/done-command-spec.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/done-command-spec.js b/spec/done-command-spec.js index 5ae596f..0ae957e 100644 --- a/spec/done-command-spec.js +++ b/spec/done-command-spec.js @@ -136,7 +136,8 @@ describe("todo.txt done command", () => { let now = `${dt.getFullYear()}-${(dt.getMonth() < 8) ? '0' : ''}${dt.getMonth() + 1}-${dt.getDate() < 10 ? '0' : ''}${dt.getDate()}`; expect(text).toEqual(`x ${now} 2017-10-17 Implement the todotxt:done command +LanguageTodotxt pri:A`); - atom.commands.dispatch(workspaceElement(), 'core:undo'); + te.undo() + text = getCurrentLine(te); expect(text).toEqual("(A) 2017-10-17 Implement the todotxt:done command +LanguageTodotxt"); }); From 2df3bd76fefba7783461059b0095df53eb705903 Mon Sep 17 00:00:00 2001 From: Matthew Crumley Date: Sun, 22 Oct 2017 19:44:25 -0400 Subject: [PATCH 14/16] Implementation --- lib/language-todotxt.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/language-todotxt.js b/lib/language-todotxt.js index 0403572..b7e9624 100644 --- a/lib/language-todotxt.js +++ b/lib/language-todotxt.js @@ -73,12 +73,12 @@ export default { } let now = this.dateNow() line = `x ${now} ${line}` - //te.getBuffer().transact(() => { + te.getBuffer().transact(() => { te.moveToBeginningOfLine() te.deleteToEndOfLine() te.insertText(line) te.moveToBeginningOfLine() - //}) + }) } }, From 7da2975ea6d8198363569fbdecc1d475b555187f Mon Sep 17 00:00:00 2001 From: Matthew Crumley Date: Tue, 23 Oct 2018 21:42:43 -0400 Subject: [PATCH 15/16] Parse due:yyyy-mm-dd correctly --- grammars/todotxt.cson | 8 ++++---- spec/grammars-todotxt-spec.js | 6 ++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/grammars/todotxt.cson b/grammars/todotxt.cson index be27366..347b1ac 100644 --- a/grammars/todotxt.cson +++ b/grammars/todotxt.cson @@ -7,6 +7,10 @@ 'match': '^\\([A-Z]\\)' 'name': 'constant.language.todotxt.priority' } + { + 'match': 'due:\\d{4}-\\d{2}-\\d{2}' + 'name': 'constant.language.todotxt.due' + } { 'match': '(\\S+)(:)(\\S+)' 'captures': { @@ -25,10 +29,6 @@ 'match': '(?:^|(?<=^\\([A-Z]\\)\\s))\\d{4}-\\d{2}-\\d{2}' 'name': 'constant.language.todotxt.date' } - { - 'match': 'due:\\d{4}-\\d{2}-\\d{2}' - 'name': 'constant.language.todotxt.due' - } { 'match': ' (\\@\\S+)' 'captures': { diff --git a/spec/grammars-todotxt-spec.js b/spec/grammars-todotxt-spec.js index 8e301a0..07169df 100644 --- a/spec/grammars-todotxt-spec.js +++ b/spec/grammars-todotxt-spec.js @@ -127,6 +127,12 @@ describe("todo.txt grammar", () => { expect(matches.length).toEqual(0); }); + it("matches due:yyyy-mm-dd properties correctly", () => { + let {tokens} = grammar.tokenizeLine("Syntax colorisation for due:2018-10-23 metadata"); + let [keyValueToken] = tokens.filter((token) => { return token.value == "due:2018-10-23"; }); + expect(keyValueToken.scopes).toEqual(["text.todotxt", "constant.language.todotxt.due"]) + }); + it("matches key:value properties correctly", () => { let {tokens} = grammar.tokenizeLine("Syntax colorisation for key:value pairs"); let [keyToken] = tokens.filter((token) => { return token.value == "key"; }); From 3f172c77908f6f3dfc17fb7d14f18d265653957f Mon Sep 17 00:00:00 2001 From: Matthew Crumley Date: Tue, 23 Oct 2018 21:55:35 -0400 Subject: [PATCH 16/16] Use date colorization for key:date Fixed #49 --- grammars/todotxt.cson | 14 ++++++++++++++ spec/grammars-todotxt-spec.js | 11 +++++++++++ 2 files changed, 25 insertions(+) diff --git a/grammars/todotxt.cson b/grammars/todotxt.cson index 347b1ac..cc4302c 100644 --- a/grammars/todotxt.cson +++ b/grammars/todotxt.cson @@ -11,6 +11,20 @@ 'match': 'due:\\d{4}-\\d{2}-\\d{2}' 'name': 'constant.language.todotxt.due' } + { + 'match': '(\\S+)(:)(\\d{4}-\\d{2}-\\d{2})' + 'captures': { + '1': { + 'name': 'property.language.todotxt.key' + } + '2': { + 'name': 'punctuation.language.todotxt.pair' + } + '3': { + 'name': 'constant.language.todotxt.date' + } + } + } { 'match': '(\\S+)(:)(\\S+)' 'captures': { diff --git a/spec/grammars-todotxt-spec.js b/spec/grammars-todotxt-spec.js index 07169df..32568ad 100644 --- a/spec/grammars-todotxt-spec.js +++ b/spec/grammars-todotxt-spec.js @@ -154,6 +154,17 @@ describe("todo.txt grammar", () => { expect(valueToken.scopes).toEqual(["text.todotxt", "value.language.todotxt.value"]) }); + it("matches key:yyyy-mm-dd correctly", () => { + let {tokens} = grammar.tokenizeLine("(B) 2017-09-20 Syntax highlighting for key:2018-10-23 metadata +LanguageTodotxt +110"); + console.log(JSON.stringify(tokens)) + let [keyToken] = tokens.filter((token) => { return token.value == "key"; }); + expect(keyToken.scopes).toEqual(["text.todotxt", "property.language.todotxt.key"]) + let [colonToken] = tokens.filter((token) => { return token.value == ":"; }); + expect(colonToken.scopes).toEqual(["text.todotxt", "punctuation.language.todotxt.pair"]) + let [valueToken] = tokens.filter((token) => { return token.value == "2018-10-23"; }); + expect(valueToken.scopes).toEqual(["text.todotxt", "constant.language.todotxt.date"]) + }); + it("matches create date without priority", () => { let {tokens} = grammar.tokenizeLine("2018-02-27 match create date without priority"); console.log(JSON.stringify(tokens))