Repository: irrationalistic/atom-tasks Branch: master Commit: a35f37e31153 Files: 20 Total size: 184.7 KB Directory structure: gitextract_py66d33m/ ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── demo.todo ├── keymaps/ │ └── tasks.cson ├── lib/ │ ├── tasks.coffee │ ├── tasks.cson │ ├── tasksUtilities.coffee │ ├── touchbar.coffee │ └── views/ │ └── task-status-view.coffee ├── menus/ │ └── tasks.cson ├── package.json ├── project.todo ├── settings/ │ └── tasks.cson ├── spec/ │ ├── benchmark.todo │ ├── complexMarkers.todo │ ├── sample.taskpaper │ └── tasks-spec.coffee └── styles/ └── tasks.less ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store npm-debug.log node_modules test.todo ================================================ FILE: CHANGELOG.md ================================================ ## 2.10.1 * Fix bug with requiring built-in season lib as opposed to node_modules one. Fixes #93 ## 2.10.0 * Fix bug with atom 1.24 new grammar system by writing dynamic grammar to intermediate file, fixes #92, #88 * Merge fix for indentation (thanks @javs) ## 2.9.1 * Fix bug with "new task" action in touchbar ## 2.9.0 * Add osx touchbar support (thanks @paulroub!) ## 2.8.0 * Escape parentheses when adding tags (thanks @paulroub), fixes #74 * Fix for timezones in tests (thanks @paulroub) ## 2.7.0 * Adds setting for applying timestamp when an item is converted to task (#76) * Re-enabled and updated all tests * Fix markdown in text * Fix completion count in taskbar (#82) * Updated moment version ## 2.6.7 * Fixes #78 (thanks @stefanthaler and @Guerillero) ## 2.6.6 * Fixes #63 where I missed the calls in the util file * Support empty lines when calculating projects (#69) ## 2.6.5 * Fix for syntax warning. Thanks to @blimmer! ## 2.6.4 * Fix for deprecated displayBuffer (#63) ## 2.6.3 * Fix for markdown in task items ## 2.6.2 * Fix for error when tasks file is in focus on load ## 2.6.1 * Fix for missing indentLevel code ## 2.6.0 * Updated after breaking changes: * Removed dependency on displayBuffer * Fixes bugs with task management * Tests are broken, so they are removed temporarily until a solution is found ## 2.5.1 * Added timestamp command to menu and readme ## 2.5.0 * Rewrote grammar to prevent extra markers from highlighting * Added support for highlighting urls inline * Added new command for adding/updating the timestamp on a task ## 2.4.0 * Added hotkey for convert-to-task ## 2.3.0 * Added support for markdown highlighting in tasks ## 2.2.0 * Added setting for controlling the attribute marker ## 2.1.0 * Added setting for controlling the archive separator ## 2.0.2 * Fix for removal of bufferColumnForToken ## 2.0.1 * Fix for bug with urls in tasks ## 2.0.0 - Big Ol Rewrite * Major rewrite of core code * Moved archive tasks hotkey to cmd-shift-a * Convert a non-task to a task * Optimized performance for larger files * Colors improved * Supports line wrapping * Added status bar item for progress * Added utility functions for managing tags * Supports complex tokens like [ ], [x] * Menu commands have spaces in names ## 1.4.1 - Readme update * Updated hotkeys in readme ## 1.4.0 - Tags for status * Now uses line tags to dictate state (improves support for taskpaper) * Project whitespace fix (thanks @JohannWeging) * Added shortcut for adding a tag above ## 1.3.0 - Wrapping support * Updated grammar to support multi-line items ## 1.2.5 - More fixes and cleanup * Removed some editor deprecations * Fixed the specs * General cleanup ## 1.2.4 - Deprecation Fixes * Updated to fix deprecation notices ## 1.2.2 - ShadowDom Support * Updated stylesheet selector to support shadow dom editor ## 1.0.1 - Fix updating to new grammar * Open todo/tasklist files will reload their grammar on settings-change ## 1.0.0 - Overhaul of Grammar * Grammar now set via code * Added ability to change markers via settings ## 0.5.0 - Added task cancelling * Users can now cancel tasks * Fixed display of context menu to only show on todo files ## 0.4.0 - Added custom date format settings * Added momentjs back in * Added custom setting for date format * Added function to convert dates in-file to format in settings ## 0.3.0 - Removed Momentjs dependency * Removed momentjs ## 0.2.1 - Changelog * Finally filled out the changelog * Fixed some formatting in readme ## 0.2.0 - Publish to Atom * First publish to atom listing ## 0.1.0 - First Release * Base syntax highlighting * Actions for adding new tasks * Actions for completing tasks * Actions for archiving ================================================ FILE: LICENSE.md ================================================ Copyright (c) 2014 Christopher Rolfs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ ## What's New in 2.0 * Archiving tasks hotkey is now `cmd-shift-a` * Can convert non-task lines to tasks * Optimized performance in larger files * Improved behind-the-scenes code * Colors improved (also tested in light themes) * Works with line wrapping * Status bar shows progress * See more in the [changelog](https://github.com/irrationalistic/atom-tasks/blob/master/CHANGELOG.md) # Tasks Package ![example](https://raw.githubusercontent.com/irrationalistic/atom-tasks/master/images/tasks_example.png) Special formatting for .todo and .taskpaper files. Allows you to easily add, complete, and archive your tasks. Adjust the settings to match your ideal style. Change all the markers to '-' to match taskpaper. Any line that ends with `:` will be considered a header (like `My Things:`) Add tags to tasks by starting them with an `@`, such as `@important` or setting a value like `@due(tuesday)`. This uses utf characters, so it is still valid as a plain text document. You can also set a custom date/time format in the settings. These can be converted in an existing document using the Tasks: Update Timestamp Format. Based off the awesome sublime text plugin https://github.com/aziz/PlainTasks ## Hotkeys ### Mac * **cmd-enter:** add a new todo item below the current * **cmd-shift-enter:** add a new todo item above the current * **cmd-d:** toggle completion of the task * **cmd-shift-a:** move all completed tasks to the archive section * **ctrl-c:** cancel the selected tasks * **ctrl-s:** add/update timestamp for current task ### PC, Linux * **ctrl-enter:** add a new todo item below the current * **ctrl-shift-enter:** add a new todo item above the current * **ctrl-d:** toggle completion of the task * **ctrl-shift-a:** move all completed tasks to the archive section * **alt-c:** cancel the selected tasks * **ctrl-shift-s:** add/update timestamp for current task ### Other Methods * **Convert to Task:** Converts a non-task line to a task * **Update Timestamps:** Attempt to convert cancelled and done tag timestamps to match the settings format ================================================ FILE: demo.todo ================================================ ☐ testing My Tasks: ☐ Implement testing for tasks package ☐ Update styles @normal(tag with value) sounds good ✔ Create TODO package for atom @done(2015-04-09 09:28) @project(Archive) v2: ✘ Have package order pizza @cancelled(2015-04-09 21:45) @project(v2) nested: ☐ Another task ================================================ FILE: keymaps/tasks.cson ================================================ # Keybindings require three things to be fully defined: A selector that is # matched against the focused element, the keystroke and the command to # execute. # # Below is a basic keybinding which registers on all platforms by applying to # the root workspace element. # For more detailed documentation see # https://atom.io/docs/latest/advanced/keymaps '.platform-darwin atom-workspace atom-text-editor:not(.mini)[data-grammar~="todo"]': 'cmd-enter': 'tasks:add' 'cmd-shift-enter': 'tasks:add-above' 'cmd-d': 'tasks:complete' 'cmd-shift-a': 'tasks:archive' 'ctrl-c': 'tasks:cancel' 'cmd-shift-l': 'tasks:convert-to-task' 'ctrl-s': 'tasks:set-timestamp' '.platform-win32 atom-workspace atom-text-editor:not(.mini)[data-grammar~="todo"], .platform-linux atom-workspace atom-text-editor:not(.mini)[data-grammar~="todo"]': 'ctrl-enter': 'tasks:add' 'ctrl-shift-enter': 'tasks:add-above' 'ctrl-d': 'tasks:complete' 'ctrl-shift-a': 'tasks:archive' 'alt-c': 'tasks:cancel' 'ctrl-shift-l': 'tasks:convert-to-task' 'ctrl-shift-s': 'tasks:set-timestamp' ================================================ FILE: lib/tasks.coffee ================================================ {Point} = require 'atom' _ = require 'underscore' CSON = require 'season' tasks = require './tasksUtilities' TaskStatusView = require './views/task-status-view' touchbar = require './touchbar' # Store the current settings for the markers marker = completeMarker = cancelledMarker = archiveSeparator = attributeMarker = '' module.exports = ### PLUGIN CONFIGURATION: ### config: dateFormat: type: 'string', default: "YYYY-MM-DD HH:mm" baseMarker: type: 'string', default: '☐' completeMarker: type: 'string', default: '✔' cancelledMarker: type: 'string', default: '✘' archiveSeparator: type: 'string', default: '___________________' attributeMarker: type: 'string', default: '@' addTimestampOnConvertToTask: type: 'boolean', default: false useTouchbar: type: 'boolean', default: false ###* * Activation of the plugin. Should set up * all listeners and force application of grammar. * @param {object} state Application state ### activate: (state) -> # Get the markers from settings marker = atom.config.get('tasks.baseMarker') completeMarker = atom.config.get('tasks.completeMarker') cancelledMarker = atom.config.get('tasks.cancelledMarker') archiveSeparator = atom.config.get('tasks.archiveSeparator') attributeMarker = atom.config.get('tasks.attributeMarker') # Whenever a marker setting changes, update the grammar atom.config.observe 'tasks.baseMarker', (val)=> marker = val; @updateGrammar() atom.config.observe 'tasks.completeMarker', (val)=> completeMarker = val; @updateGrammar() atom.config.observe 'tasks.cancelledMarker', (val)=> cancelledMarker = val; @updateGrammar() atom.config.observe 'tasks.archiveSeparator', (val)=> archiveSeparator = val atom.config.observe 'tasks.attributeMarker', (val)=> attributeMarker = val; @updateGrammar() # Update the grammar when activated @updateGrammar() # Set up the command list atom.commands.add 'atom-text-editor', "tasks:add": => @newTask() "tasks:add-above": => @newTask(-1) "tasks:complete": => @completeTask() "tasks:archive": => @tasksArchive() "tasks:update-timestamps": => @tasksUpdateTimestamp() "tasks:cancel": => @cancelTask() "tasks:convert-to-task": => @convertToTask() "tasks:set-timestamp": => @setTimestamp() config = atom.config.get('tasks') @useTouchbar = config.useTouchbar atom.config.onDidChange 'tasks.useTouchbar', ({newValue, oldValue}) => @useTouchbar = newValue if newValue != oldValue @updateTouchbar() @activeItemSub = atom.workspace.onDidChangeActivePaneItem => @subscribeToActiveTextEditor() @subscribeToActiveTextEditor() ###* * Dynamically update the grammar CSON file * to support user-set values for markers. ### updateGrammar: -> # Escape a string clean = (str)-> for pat in ['\\', '/', '[', ']', '*', '.', '+', '(', ')'] str = str.replace pat, '\\' + pat str # Replace given string's markers rep = (prop)-> str = prop str = str.replace '☐', clean marker str = str.replace '✔', clean completeMarker str = str.replace '✘', clean cancelledMarker str = str.replace '@', clean attributeMarker # Load in the grammar manually and do replacement g = CSON.readFileSync __dirname + '/tasks.cson' # g.repository.marker.match = rep g.repository.marker.match g.repository.attribute.match = rep g.repository.attribute.match g.patterns = g.patterns.map (pattern) -> pattern.match = rep pattern.match if pattern.match pattern.begin = rep pattern.begin if pattern.begin pattern # first, clear existing grammar atom.grammars.removeGrammarForScopeName 'source.todo' # Write updated grammar to file, then update the atom grammar tasks.writeGrammarSync(g) newGrammar = atom.grammars.loadGrammarSync tasks.grammarPath atom.grammars.addGrammar newGrammar ###* * Helper for handling the status bar * @param {object} statusBar The statusbar ### consumeStatusBar: (statusBar) -> @taskStatus = new TaskStatusView() @taskStatus.initialize() @statusBarTile = statusBar.addLeftTile(item: @taskStatus, priority: 100) getActiveTextEditor: -> @editor = atom.workspace.getActiveTextEditor() ###* * Handle deactivation of the plugin. Remove * all listeners and connections ### deactivate: -> @statusBarTile?.destroy() @statusBarTile = null @activeItemSub.dispose() @moveSub?.dispose() ###* * Watch updates on our editor-in-progress ### subscribeToActiveTextEditor: -> @moveSub?.dispose() @moveSub = @getActiveTextEditor()?.onDidChangeCursorPosition => pos = @getActiveTextEditor()?.getCursorBufferPosition() if pos.row != @lastLine @lastLine = pos.row @updateTouchbar() @updateTouchbar() ###* * Update our active buttons based on the current line. ### updateTouchbar: -> if @useTouchbar && tasks.checkIsTasks() pt = @editor.getCursorBufferPosition() config = atom.config.get('tasks') lineInfo = tasks.parseLine @editor, pt.row, config lineInfo.wantArchive = @wantArchive touchbar.update lineInfo, (action) => switch action when "complete" then @completeTask() when "new" then @newTask() when "cancel" then @cancelTask() when "convert" then @convertToTask() when "archive" then @archiveTasks() else touchbar.update({}, null) ###* * Add a new todo item with the base marker * @param {number} direction = 1 Defines whether this should * be above or below the cursor ### newTask: (direction = 1)-> editor = atom.workspace.getActiveTextEditor() return if not editor editor.transact -> pos = editor.getCursorBufferPosition() info = tasks.parseLine editor, pos.row, atom.config.get('tasks') editor.insertNewlineBelow() if direction is 1 editor.insertNewlineAbove() if direction is -1 editor.insertText "#{marker} " ###* * Helper for completing a task ### completeTask: -> editor = atom.workspace.getActiveTextEditor() return if not editor selection = editor.getSelectedBufferRanges() editor.transact -> tasks.getAllSelectionRows(selection).map (row)-> info = tasks.parseLine editor, row, atom.config.get('tasks') markerToken = info.marker doneToken = _.find info.tags, (tag) -> tag.tagName.value is 'done' if markerToken and not doneToken # This is a task and isn't already done, # so calculate the projects this task # belongs to. projects = tasks.getProjects editor, row .map (p)-> tasks.parseProjectName p .reverse() # Clear any cancelled information beforehand tasks.removeTag editor, info, 'cancelled' info = tasks.parseLine editor, row, atom.config.get('tasks') tasks.removeTag editor, info, 'project' info = tasks.parseLine editor, row, atom.config.get('tasks') # Add the tag and the projects, if there are any tasks.addTag editor, row, attributeMarker, 'done', tasks.getFormattedDate() if projects.length tasks.addTag editor, row, attributeMarker, 'project', projects.join ' / ' info = tasks.parseLine editor, row, atom.config.get('tasks') tasks.setMarker editor, info, completeMarker else if markerToken and doneToken # This task was previously completed, so # just need to clear out the tags tasks.removeTag editor, info, 'done' info = tasks.parseLine editor, row, atom.config.get('tasks') tasks.removeTag editor, info, 'project' info = tasks.parseLine editor, row, atom.config.get('tasks') tasks.setMarker editor, info, marker ###* * Helper for cancelling a task ### cancelTask: -> editor = atom.workspace.getActiveTextEditor() return if not editor selection = editor.getSelectedBufferRanges() editor.transact -> tasks.getAllSelectionRows(selection).map (row)-> info = tasks.parseLine editor, row, atom.config.get('tasks') markerToken = info.marker cancelledToken = _.find info.tags, (tag) -> tag.tagName.value is 'cancelled' if markerToken and not cancelledToken # This is a task and isn't already cancelled, # so calculate the projects this task # belongs to. projects = tasks.getProjects editor, row .map (p)-> tasks.parseProjectName p .reverse() # Clear any done information beforehand tasks.removeTag editor, info, 'done' info = tasks.parseLine editor, row, atom.config.get('tasks') tasks.removeTag editor, info, 'project' # Add the tag and the projects, if there are any tasks.addTag editor, row, attributeMarker, 'cancelled', tasks.getFormattedDate() if projects.length tasks.addTag editor, row, attributeMarker, 'project', projects.join ' / ' info = tasks.parseLine editor, row, atom.config.get('tasks') tasks.setMarker editor, info, cancelledMarker else if markerToken and cancelledToken # This task was previously completed, so # just need to clear out the tags tasks.removeTag editor, info, 'cancelled' info = tasks.parseLine editor, row, atom.config.get('tasks') tasks.removeTag editor, info, 'project' info = tasks.parseLine editor, row, atom.config.get('tasks') tasks.setMarker editor, info, marker ###* * Helper for updating timestamps to match * the given settings ### tasksUpdateTimestamp: -> # Update timestamps to match the current setting (only for tags though) editor = atom.workspace.getActiveTextEditor() return if not editor selection = editor.getSelectedBufferRanges() editor.transact -> tasks.getAllSelectionRows(selection).map (row)-> # These tags will receive updated timestamps # based on existing ones tagsToUpdate = ['done', 'cancelled', 'timestamp'] for tag in tagsToUpdate info = tasks.parseLine editor, row, atom.config.get('tasks') curDateTag = _.find info.tags, (t) -> t.tagName.value is tag curDate = curDateTag?.tagValue.value if curDate tasks.updateTag editor, info, attributeMarker, tag, tasks.getFormattedDate(curDate) ###* * Helper for converting a non-task * line to a task ### convertToTask: -> editor = atom.workspace.getActiveTextEditor() return if not editor selection = editor.getSelectedBufferRanges() editor.transact => tasks.getAllSelectionRows(selection).map (row) => info = tasks.parseLine editor, row, atom.config.get('tasks') if info.type is 'text' # Only set the marker if this isn't # already a task or header. tasks.setMarker editor, info, marker if atom.config.get('tasks.addTimestampOnConvertToTask') tasks.addTag editor, row, attributeMarker, 'timestamp', tasks.getFormattedDate() @updateTouchbar() ###* * Helper for setting the timestamp on a task. If it exists, remove and * reset it. If it doesn't exist, add it. This will also update the timestamp * for tasks that have 'done' or 'cancelled' ### setTimestamp: -> editor = atom.workspace.getActiveTextEditor() return if not editor selection = editor.getSelectedBufferRanges() editor.transact -> tasks.getAllSelectionRows(selection).map (row)-> info = tasks.parseLine editor, row, atom.config.get('tasks') if info.marker doneTag = _.find info.tags, (t) -> t.tagName.value is 'done' cancelledTag = _.find info.tags, (t) -> t.tagName.value is 'cancelled' timestampTag = _.find info.tags, (t) -> t.tagName.value is 'timestamp' curDate = tasks.getFormattedDate() if not doneTag and not cancelledTag and not timestampTag tasks.addTag editor, row, attributeMarker, 'timestamp', curDate else tasks.updateTag editor, info, attributeMarker, 'done', curDate info = tasks.parseLine editor, row, atom.config.get('tasks') tasks.updateTag editor, info, attributeMarker, 'cancelled', curDate info = tasks.parseLine editor, row, atom.config.get('tasks') tasks.updateTag editor, info, attributeMarker, 'timestamp', curDate ###* * Helper for handling the archiving of * all done and cancelled tasks ### tasksArchive: -> editor = atom.workspace.getActiveTextEditor() return if not editor editor.transact -> completedTasks = [] archiveProject = null insertRow = -1 # 1. Find the archives section, if it exists editor.buffer.getLines().every (i, ind)-> # if we already found the archive, no need # to parse any more! return false if archiveProject info = tasks.parseLine editor, ind, atom.config.get('tasks') hasDone = _.some info.tags, (t) -> t.tagName.value is 'done' hasCancelled = _.some info.tags, (t) -> t.tagName.value is 'cancelled' hasArchive = info.project is 'Archive' el = lineNumber: ind line: i archiveProject = el if hasArchive completedTasks.push el if hasDone or hasCancelled true # 2. I have a list of all completed tasks, # as well as where the archive exists, if it does if not archiveProject # no archive? create it! archiveText = """ #{archiveSeparator} Archive: """ # Before adding the final archive section, # we should clear out the empty lines at # the end of the file. for line, i in editor.buffer.getLines() by -1 if editor.buffer.isRowBlank i # remove the line editor.buffer.deleteRow i else break # add to the end of the file newRange = editor.buffer.append archiveText insertRow = newRange.end.row else insertRow = archiveProject.lineNumber + 1 # 3. Archive insertion point is ready! Let's # start copying down the completed items. completedTasks.reverse() insertPoint = new Point insertRow, 0 indentation = editor.buildIndentString 1 completedTasks.forEach (i)-> editor.buffer.insert insertPoint, "#{indentation}#{i.line.trim()}\n" # 4. Copy is completed, start deleting the # copied items completedTasks.forEach (i)-> editor.buffer.deleteRow i.lineNumber ================================================ FILE: lib/tasks.cson ================================================ 'fileTypes': [ 'todo' 'taskpaper' ] 'scopeName': 'source.todo' 'name': 'Tasks' 'patterns': [ { 'match': '^([\\s]*)(.*):$' 'name': 'control.tasks.header.${2:/downcase}' 'captures': '2': 'name': 'control.tasks.header-title' } { 'begin': '^([\\s]*)(✔)(?=.*@done)' 'end': '$' 'name': 'tasks.text.done' 'beginCaptures': '2': 'name': 'keyword.tasks.marker' 'patterns': [ {'include': '#attribute'} ] } { 'begin': '^([\\s]*)(✘)(?=.*@cancelled)' 'end': '$' 'name': 'tasks.text.cancelled' 'beginCaptures': '2': 'name': 'keyword.tasks.marker' 'patterns': [ {'include': '#attribute'} ] } { 'begin': '^([\\s]*)(☐)' 'end': '$' 'name': 'tasks.text' 'beginCaptures': '2': 'name': 'keyword.tasks.marker' 'patterns': [ {'include': '#attribute'}, {'include': 'text.md'} {'include': 'text.hyperlink'} ] } ] 'repository': 'attribute': 'match': '@([\\w]+)(?:\\((.*?)\\))?' 'name': 'tasks.attribute.$1' 'captures': '1': 'name': 'tasks.attribute-name' '2': 'name': 'tasks.attribute-value' ================================================ FILE: lib/tasksUtilities.coffee ================================================ {Point, Range} = require 'atom' _ = require 'underscore' CSON = require 'season' moment = require 'moment' path = require 'path' # ATTRIBUTE_RX = /( ?)(@[ ]?(([\w]+)(\((.*?)\))?))/gi module.exports = markerSelector: 'keyword.tasks.marker' doneSelector: 'tasks.text.done' cancelledSelector: 'tasks.text.cancelled' archiveSelector: 'control.tasks.header.archive' headerSelector: 'control.tasks.header-title' grammarPath: path.join process.env.ATOM_HOME, 'TasksGrammar.cson' # Escape a string cleanRegex: (str)-> for pat in ['\\', '/', '[', ']', '*', '.', '+', '(', ')'] str = str.replace pat, '\\' + pat str ###* * Write a new grammar file to disk so that it can be loaded in * dynamically. * @param {Object} grammarObj The object to write to disk representing * the grammar content as CSON * @return void ### writeGrammarSync: (grammarObj) -> CSON.writeFileSync @grammarPath, grammarObj parseLine: (editor, lineNumber, config) -> whiteRx = /^\s*/ projectRx = /^\s*(.*):$/ baseMarker = @cleanRegex config.baseMarker completeMarker = @cleanRegex config.completeMarker cancelledMarker = @cleanRegex config.cancelledMarker taskRx = new RegExp "^(\\s*)(#{baseMarker}|#{completeMarker}|#{cancelledMarker})(.*)$" line = editor.buffer.lineForRow lineNumber indentation = editor.indentationForBufferRow lineNumber result = lineNumber: lineNumber line: line indentation: indentation firstNonWhitespaceIndex: line.match(whiteRx)[0].length type = 'text' if projectRx.test line type = 'project' match = line.match projectRx result.project = match[1] else if taskRx.test line type = 'task' match = line.match taskRx result.marker = value: match[2] range: new Range new Point(lineNumber, match[1].length), new Point(lineNumber, match[1].length + match[2].length) result.text = match[3].trim() result.tags = @getAllTags editor, lineNumber, config.attributeMarker result.type = type return result ###* * Get all the tags on a given line * @param {TextEditor} editor Editor to use * @param {Number} lineNumber Number of line ### getAllTags: (editor, lineNumber, attributeMarker)-> tags = [] # lines = editor.displayBuffer.tokenizedBuffer.tokenizedLines # checkLine = lines[lineNumber] checkLine = editor.buffer.lineForRow lineNumber attributeRX = new RegExp "( ?)(\\#{attributeMarker}[ ]?(([\\w]+)(\\((.*?[^\\\\])\\))?))", 'gi' while match = attributeRX.exec checkLine sPt = new Point lineNumber, match.index ePt = new Point lineNumber, match.index + match[0].length nameStart = new Point( lineNumber, match.index + match[0].indexOf match[4] ) nameEnd = new Point( lineNumber, match.index + match[0].indexOf(match[4]) + match[4].length ) if match[6] valueStart = new Point( lineNumber, match.index + match[0].indexOf match[6] ) valueEnd = new Point( lineNumber, match.index + match[0].indexOf(match[6]) + match[6].length ) tags.push tagName: value: match[4] range: new Range nameStart, nameEnd tagValue: value: match[6] range: new Range valueStart, valueEnd range: new Range sPt, ePt tags ###* * Get a specific tag from the given line * @param {TextEditor} editor Editor to use * @param {Number} lineNumber Line number to use * @param {String} tagName Tag to find * @param {String} attributeMarker Marker character in use ### getTag: (editor, lineNumber, tagName, attributeMarker)-> tags = @getAllTags editor, lineNumber, attributeMarker _.find tags, (t)->t.tagName.value is tagName ###* * Helper for adding a tag/value to a given line * @param {TextEditor} editor Editor to use * @param {Number} lineNumber Line number to use * @param {String} attributeMarker Marker being used for attributes * @param {String} tagName Name of tag * @param {String} tagValue Value of tag (optional) ### addTag: (editor, lineNumber, attributeMarker, tagName, tagValue)-> point = new Point lineNumber, editor.buffer.lineLengthForRow lineNumber if tagValue safeTagValue = @escapeTagValue tagValue editor.buffer.insert point, " #{attributeMarker}#{tagName}(#{safeTagValue})" else editor.buffer.insert point, " #{attributeMarker}#{tagName}" escapeTagValue: (tagValue) -> tagValue.replace(/\(/g, "\\(").replace(/\)/g, "\\)") ###* * Helper for removing a tag by name * @param {TextEditor} editor Editor to use * @param {Number} lineNumber Line number to remove from * @param {String} tagName Tag name to remove * @param {String} attributeMarker Marker character in use ### removeTag: (editor, info, tagName)-> match = _.find info.tags, (i)->i.tagName.value is tagName editor.buffer.delete match.range if match ###* * Helper for updating the value of a tag * @param {TextEditor} editor Editor to use * @param {Number} lineNumber Line number to update on * @param {String} attributeMarker Marker character in use * @param {String} tagName Tag name to update value of * @param {String} newTagValue New value of tag (optional). * Leave undefined to remove value ### updateTag: (editor, info, attributeMarker, tagName, newTagValue)-> tag = _.find info.tags, (t) -> t.tagName.value is tagName return if not tag if newTagValue if tag.tagValue.range.isEmpty() pt = tag.tagName.range.end editor.buffer.insert pt, "(#{newTagValue})" else editor.buffer.setTextInRange tag.tagValue.range, newTagValue else if not tag.tagValue.range.isEmpty() tag.tagValue.range.start.column-- tag.tagValue.range.end.column++ editor.buffer.delete tag.tagValue.range ###* * Find the token on the line given a css-like selector * @param {Array} tokens Array of tokens to look through * @param {String} selector CSS-like selector to search for ### getToken: (tokens, selector)-> for token in tokens return token if selector in token.scopes null ###* * Given a token search string, find all * lines in the given editor that match * @param {TextEditor} editor Editor to use * @param {String} toFind CSS-like selector to search for ### getLinesByToken: (editor, toFind)-> editor.tokenizedBuffer.tokenizedLines.filter (i)=> @getToken i.tokens, toFind ###* * Helper for finding all parent nodes of this line * that are projects * @param {TextEditor} editor Editor to use * @param {Number} lineNumber Line number to start at ### getProjects: (editor, lineNumber)-> lines = editor.tokenizedBuffer.tokenizedLines projects = [] curHeaderLevel = editor.indentationForBufferRow(lineNumber) return projects if lineNumber is 0 for row in [lineNumber-1..0] curLine = lines[row] if editor.indentationForBufferRow(row) < curHeaderLevel if @getToken curLine.tokens, @headerSelector projects.push curLine curHeaderLevel = editor.indentationForBufferRow(row) rowIsZero = editor.indentationForBufferRow(row) is 0 rowIsEmpty = editor.isBufferRowBlank(row) break if rowIsZero and not rowIsEmpty projects ###* * Given a project line, parse out just the name * @param {TokenizedLine} line TokenizedLine to parse ### parseProjectName: (line)-> match = @getToken line.tokens, @headerSelector match.value ###* * Helper for getting the rows of a selection, * which can be made up of an array of ranges. * @param {Array} selection Array of ranges ### getAllSelectionRows: (selection)-> _.flatten selection.map (s)->s.getRows() ###* * Helper for getting the date based on given settings * @return {Date/String} Date or date string to use ### getFormattedDate: (date = Date.now())-> moment(date).format(atom.config.get('tasks.dateFormat')) ###* * Helper for setting the marker of a given line * @param {TextEditor} editor Editor to use * @param {Number} lineNumber Line number to use * @param {String} markerText New marker to set ### setMarker: (editor, info, markerText)-> # given some line, change the marker # if it exists, or add one if it doesn't if info.marker editor.buffer.setTextInRange info.marker.range, markerText else # need to insert the marker pt = new Point info.lineNumber, info.firstNonWhitespaceIndex editor.buffer.insert pt, markerText + ' ' ###* * Determine if the currently-edited buffer is a task document ### checkIsTasks: -> editor = atom.workspace.getActiveTextEditor() return editor?.getGrammar().name is 'Tasks' ================================================ FILE: lib/touchbar.coffee ================================================ tasks = require './tasksUtilities' {TouchBar} = require('remote') {TouchBarButton, TouchBarSpacer} = TouchBar module.exports = update: (info, callback) -> return if not TouchBar window = atom.getCurrentWindow() if not info.type || not tasks.checkIsTasks() window.setTouchBar(null) return buttons = [] config = atom.config.get('tasks') isTask = info.type == 'task' if isTask completed = info.marker.value in [config.completeMarker, config.cancelledMarker] buttons.push @callbackButton(config.baseMarker, "New", callback, '#5293d8') buttons.push new TouchBarSpacer({size: 'small'}) if isTask && ! completed buttons.push @callbackButton(config.completeMarker, "Complete", callback, '#45A815') buttons.push new TouchBarSpacer({size: 'small'}) buttons.push @callbackButton(config.cancelledMarker, "Cancel", callback, '#CD8E00') buttons.push new TouchBarSpacer({size: 'small'}) if ! isTask && /\S/.test(info.line) buttons.push @callbackButton(config.baseMarker, "Convert", callback) buttons.push new TouchBarSpacer({size: 'small'}) touchBar = new TouchBar({items: buttons}) window.setTouchBar(touchBar) callbackButton: (icon, command, callback, bgcolor) -> opts = { label: "#{icon} #{command}", click: () => callback(command.toLowerCase()) } if bgcolor opts.backgroundColor = bgcolor return new TouchBarButton(opts) ================================================ FILE: lib/views/task-status-view.coffee ================================================ tasks = require '../tasksUtilities' _ = require 'underscore' class TaskStatusView extends HTMLElement initialize: -> @classList.add('task-status', 'inline-block') @style.display = 'none' @activeItemSub = atom.workspace.onDidChangeActivePaneItem => @subscribeToActiveTextEditor() @subscribeToActiveTextEditor() destroy: -> @activeItemSub.dispose() @changeSub?.dispose() @tokenizeSub?.dispose() subscribeToActiveTextEditor: -> @changeSub?.dispose() @changeSub = @getActiveTextEditor()?.onDidStopChanging => @updateStatus() @tokenizeSub?.dispose() @tokenizeSub = @getActiveTextEditor()?.tokenizedBuffer .onDidTokenize => @updateStatus() @updateStatus() getActiveTextEditor: -> @editor = atom.workspace.getActiveTextEditor() checkIsTasks: -> if tasks.checkIsTasks() @style.display = '' return true @style.display = 'none' false updateStatus: -> if @checkIsTasks() tokenizedLines = @editor.tokenizedBuffer.tokenizedLines info = _.countBy tokenizedLines, (line)-> return 'text' if not line hasMarker = tasks.getToken line.tokens, tasks.markerSelector hasDone = tasks.getToken line.tokens, tasks.doneSelector hasCancelled = tasks.getToken line.tokens, tasks.cancelledSelector hasProject = tasks.getToken line.tokens, tasks.headerSelector return 'project' if hasProject return 'done' if hasDone return 'cancelled' if hasCancelled return 'task' if hasMarker return 'text' _.defaults info, done: 0, cancelled: 0 project: 0, task: 0 text: 0 completed = info.done + info.cancelled total = completed + info.task completed = '-' if isNaN completed total = '-' if isNaN total @textContent = "(#{completed}/#{total})" module.exports = document.registerElement 'status-bar-tasks', prototype: TaskStatusView.prototype, extends: 'div' ================================================ FILE: menus/tasks.cson ================================================ 'context-menu': 'atom-text-editor[data-grammar~="todo"]': [ {'type': 'separator'} { 'label': 'Add Task Below', 'command': 'tasks:add' } { 'label': 'Add Task Above', 'command': 'tasks:add-above' } { 'label': 'Complete Task', 'command': 'tasks:complete' } { 'label': 'Cancel Task', 'command': 'tasks:cancel' } { 'label': 'Archive Tasks', 'command': 'tasks:archive' } { 'label': 'Convert to Task', 'command': 'tasks:convert-to-task' } { 'label': 'Update Timestamp Format', 'command': 'tasks:update-timestamps' } { 'label': 'Add/Update Current Timestamp', 'command': 'tasks:set-timestamp' } {'type': 'separator'} ] ================================================ FILE: package.json ================================================ { "name": "tasks", "main": "./lib/tasks", "version": "2.10.1", "description": "Manage your todo lists", "keywords": [ "tasks", "todo", "productivity", "utilities", "taskpaper" ], "repository": "https://github.com/irrationalistic/atom-tasks", "license": "MIT", "engines": { "atom": ">= 1.13" }, "dependencies": { "moment": "~2.11.2", "underscore": "^1.8.3", "season": "~6.0.2" }, "consumedServices": { "status-bar": { "versions": { "^1.0.0": "consumeStatusBar" } } } } ================================================ FILE: project.todo ================================================ v3: ☐ Add more test coverage for utilities ☐ Export functionality as a service? ☐ Add pull-request to symbols view package ☐ Add line-level decorations for given tags (#22) ☐ Plan for query language system (#19) ___________________ Archive: ✔ Make value optional in addTag @done(2015-04-12 22:14) @project(v2) ✔ Make value optional in updateTag (also support not having a value previously) @done(2015-04-12 22:14) @project(v2) ✔ Clean up tasks.coffee file @done(2015-04-12 11:21) @project(v2) ✔ Fix for projects on own line @done(2015-04-11 17:20) @project(v2) ✔ Add support for complex markers (like [ ] and [x]) @done(2015-04-11 16:56) @project(v2) ✔ Support markers with different sizes @done(2015-04-11 16:56) @project(v2) ✔ Add package keywords @done(2015-04-11 16:28) @project(v2) ✔ Add progress to status bar @done(2015-04-11 16:28) @project(v3) ✔ Improving token matching @done(2015-04-11 10:53) @project(v2) ✔ Update menu @done(2015-04-10 21:55) @project(v2) ✔ Add benchmark @done(2015-04-10 21:52) @project(v2) ✔ Fix up tests @done(2015-04-10 21:35) @project(v2) ✔ Archive should minimize spacing on remaining lines (no more than one line away from last real line) @done(2015-04-10 12:21) @project(v2) ✔ Update readme @done(2015-04-10 11:37) @project(v2) ✘ Add method for updating all markers to new values @cancelled(2015-04-09 22:18) @project(v2) ✘ Fix awkward coloring in minimap @cancelled(2015-04-09 22:07) @project(v2) ✔ Clear out unnecessary files @done(2015-04-09 22:06) @project(v2) ✔ Commands need spaces in name @done(2015-04-09 22:04) @project(v2) ✘ Prevent duplicate adding task attribute @cancelled(2015-04-09 22:00) @project(v2) ✘ Better time printing when updating timestamp @cancelled(2015-04-09 22:00) @project(v2) ✔ Don't add project tag to things without projects when completed @done(2015-04-09 21:47) @project(v2) ✔ Cancelling a done task or vice-versa @done(2015-04-09 21:45) @project(v2) ✔ Test with light syntax theme @done(2015-04-09 21:42) @project(v2) ✔ Support for wrapping @done(2015-04-09 21:40) @project(v2) ✔ Updating marker helper @done(2015-04-09 16:39) @project(v2) ✔ Converting to task from non-task @done(2015-04-09 09:46) @project(v2) ✔ Improve archiving tasks @done(2015-04-09 21:19) @project(v2) ✔ Converting a task should ignore project lines @done(2015-04-09 10:09) @project(v2) ✔ Update timestamps should work on tasks without timestamp @done(2015-04-09 09:28) @project(v2) ✔ Update timestamps should work on cancelled items @done(2015-04-09 09:28) @project(v2) ✔ Update new task method @done(2015-04-09 15:52) @project(v2) ✔ Support non-indented tasks @done(2015-04-09 15:53) @project(v2) ================================================ FILE: settings/tasks.cson ================================================ '.source.todo, .source.taskpaper': 'editor': 'autoIndentOnPaste': false 'increaseIndentPattern': '^.*:$' ================================================ FILE: spec/benchmark.todo ================================================ Project 0: ☐ test 1 ☐ test 2 ☐ test 3 ☐ test 4 ☐ test 5 ☐ test 6 ☐ test 7 ☐ test 8 ☐ test 9 ☐ test 10 ☐ test 11 Project 12: ☐ test 13 ☐ test 14 ☐ test 15 ☐ test 16 ☐ test 17 ☐ test 18 ☐ test 19 ☐ test 20 ☐ test 21 Project 22: ☐ test 23 ☐ test 24 ☐ test 25 ☐ test 26 ☐ test 27 Project 28: ☐ test 29 ☐ test 30 ☐ test 31 ☐ test 32 ☐ test 33 ☐ test 34 ☐ test 35 ☐ test 36 ☐ test 37 ☐ test 38 ☐ test 39 ☐ test 40 ☐ test 41 ☐ test 42 ☐ test 43 ☐ test 44 ☐ test 45 Project 46: ☐ test 47 ☐ test 48 ☐ test 49 ☐ test 50 ☐ test 51 ☐ test 52 ☐ test 53 ☐ test 54 ☐ test 55 ☐ test 56 ☐ test 57 ☐ test 58 ☐ test 59 ☐ test 60 Project 61: ☐ test 62 ☐ test 63 ☐ test 64 ☐ test 65 ☐ test 66 ☐ test 67 Project 68: ☐ test 69 ☐ test 70 ☐ test 71 ☐ test 72 ☐ test 73 ☐ test 74 Project 75: ☐ test 76 ☐ test 77 ☐ test 78 Project 79: ☐ test 80 ☐ test 81 ☐ test 82 ☐ test 83 ☐ test 84 ☐ test 85 ☐ test 86 ☐ test 87 ☐ test 88 Project 89: ☐ test 90 ☐ test 91 ☐ test 92 ☐ test 93 ☐ test 94 ☐ test 95 ☐ test 96 Project 97: ☐ test 98 ☐ test 99 ☐ test 100 ☐ test 101 ☐ test 102 ☐ test 103 ☐ test 104 ☐ test 105 ☐ test 106 ☐ test 107 ☐ test 108 ☐ test 109 ☐ test 110 ☐ test 111 ☐ test 112 ☐ test 113 ☐ test 114 Project 115: ☐ test 116 ☐ test 117 ☐ test 118 ☐ test 119 ☐ test 120 ☐ test 121 Project 122: ☐ test 123 ☐ test 124 ☐ test 125 ☐ test 126 ☐ test 127 ☐ test 128 ☐ test 129 ☐ test 130 ☐ test 131 ☐ test 132 ☐ test 133 ☐ test 134 ☐ test 135 ☐ test 136 ☐ test 137 Project 138: ☐ test 139 ☐ test 140 ☐ test 141 ☐ test 142 ☐ test 143 ☐ test 144 ☐ test 145 ☐ test 146 ☐ test 147 ☐ test 148 Project 149: ☐ test 150 ☐ test 151 ☐ test 152 ☐ test 153 ☐ test 154 ☐ test 155 ☐ test 156 ☐ test 157 Project 158: ☐ test 159 ☐ test 160 ☐ test 161 ☐ test 162 ☐ test 163 ☐ test 164 ☐ test 165 ☐ test 166 ☐ test 167 ☐ test 168 ☐ test 169 ☐ test 170 ☐ test 171 ☐ test 172 ☐ test 173 ☐ test 174 ☐ test 175 ☐ test 176 ☐ test 177 ☐ test 178 ☐ test 179 ☐ test 180 ☐ test 181 ☐ test 182 ☐ test 183 ☐ test 184 Project 185: ☐ test 186 ☐ test 187 ☐ test 188 ☐ test 189 ☐ test 190 Project 191: ☐ test 192 ☐ test 193 ☐ test 194 Project 195: ☐ test 196 ☐ test 197 ☐ test 198 ☐ test 199 ☐ test 200 ☐ test 201 ☐ test 202 ☐ test 203 Project 204: ☐ test 205 ☐ test 206 ☐ test 207 ☐ test 208 Project 209: ☐ test 210 ☐ test 211 ☐ test 212 ☐ test 213 ☐ test 214 ☐ test 215 ☐ test 216 Project 217: ☐ test 218 ☐ test 219 Project 220: ☐ test 221 ☐ test 222 Project 223: ☐ test 224 ☐ test 225 ☐ test 226 Project 227: ☐ test 228 ☐ test 229 ☐ test 230 ☐ test 231 ☐ test 232 ☐ test 233 ☐ test 234 ☐ test 235 ☐ test 236 ☐ test 237 ☐ test 238 ☐ test 239 ☐ test 240 ☐ test 241 ☐ test 242 ☐ test 243 ☐ test 244 ☐ test 245 Project 246: ☐ test 247 ☐ test 248 ☐ test 249 Project 250: ☐ test 251 ☐ test 252 ☐ test 253 ☐ test 254 ☐ test 255 ☐ test 256 ☐ test 257 ☐ test 258 ☐ test 259 ☐ test 260 Project 261: ☐ test 262 ☐ test 263 ☐ test 264 ☐ test 265 ☐ test 266 ☐ test 267 ☐ test 268 ☐ test 269 ☐ test 270 ☐ test 271 ☐ test 272 Project 273: ☐ test 274 ☐ test 275 ☐ test 276 ☐ test 277 ☐ test 278 ☐ test 279 ☐ test 280 ☐ test 281 ☐ test 282 Project 283: ☐ test 284 ☐ test 285 Project 286: ☐ test 287 Project 288: ☐ test 289 ☐ test 290 ☐ test 291 ☐ test 292 ☐ test 293 ☐ test 294 ☐ test 295 Project 296: ☐ test 297 ☐ test 298 ☐ test 299 ☐ test 300 ☐ test 301 ☐ test 302 ☐ test 303 Project 304: ☐ test 305 ☐ test 306 ☐ test 307 ☐ test 308 ☐ test 309 ☐ test 310 ☐ test 311 ☐ test 312 ☐ test 313 ☐ test 314 ☐ test 315 ☐ test 316 Project 317: ☐ test 318 ☐ test 319 ☐ test 320 ☐ test 321 ☐ test 322 ☐ test 323 Project 324: ☐ test 325 ☐ test 326 Project 327: ☐ test 328 ☐ test 329 ☐ test 330 ☐ test 331 ☐ test 332 Project 333: ☐ test 334 ☐ test 335 ☐ test 336 ☐ test 337 ☐ test 338 ☐ test 339 ☐ test 340 ☐ test 341 ☐ test 342 ☐ test 343 Project 344: ☐ test 345 ☐ test 346 Project 347: ☐ test 348 Project 349: ☐ test 350 ☐ test 351 ☐ test 352 ☐ test 353 Project 354: ☐ test 355 ☐ test 356 ☐ test 357 ☐ test 358 ☐ test 359 ☐ test 360 ☐ test 361 ☐ test 362 ☐ test 363 Project 364: ☐ test 365 ☐ test 366 ☐ test 367 Project 368: Project 369: ☐ test 370 ☐ test 371 ☐ test 372 ☐ test 373 ☐ test 374 ☐ test 375 ☐ test 376 ☐ test 377 ☐ test 378 ☐ test 379 ☐ test 380 ☐ test 381 ☐ test 382 ☐ test 383 ☐ test 384 ☐ test 385 ☐ test 386 ☐ test 387 ☐ test 388 ☐ test 389 Project 390: ☐ test 391 ☐ test 392 ☐ test 393 ☐ test 394 ☐ test 395 ☐ test 396 ☐ test 397 ☐ test 398 ☐ test 399 Project 400: ☐ test 401 Project 402: Project 403: ☐ test 404 ☐ test 405 ☐ test 406 Project 407: ☐ test 408 ☐ test 409 ☐ test 410 ☐ test 411 ☐ test 412 ☐ test 413 ☐ test 414 ☐ test 415 ☐ test 416 Project 417: ☐ test 418 ☐ test 419 ☐ test 420 ☐ test 421 ☐ test 422 Project 423: ☐ test 424 ☐ test 425 ☐ test 426 ☐ test 427 ☐ test 428 ☐ test 429 ☐ test 430 ☐ test 431 ☐ test 432 ☐ test 433 ☐ test 434 ☐ test 435 ☐ test 436 ☐ test 437 Project 438: ☐ test 439 ☐ test 440 ☐ test 441 ☐ test 442 ☐ test 443 ☐ test 444 ☐ test 445 Project 446: ☐ test 447 Project 448: ☐ test 449 ☐ test 450 ☐ test 451 ☐ test 452 ☐ test 453 ☐ test 454 ☐ test 455 ☐ test 456 ☐ test 457 ☐ test 458 ☐ test 459 ☐ test 460 ☐ test 461 ☐ test 462 ☐ test 463 ☐ test 464 ☐ test 465 ☐ test 466 Project 467: ☐ test 468 ☐ test 469 Project 470: ☐ test 471 ☐ test 472 ☐ test 473 ☐ test 474 ☐ test 475 Project 476: ☐ test 477 ☐ test 478 ☐ test 479 ☐ test 480 ☐ test 481 ☐ test 482 ☐ test 483 ☐ test 484 ☐ test 485 ☐ test 486 ☐ test 487 ☐ test 488 ☐ test 489 ☐ test 490 ☐ test 491 ☐ test 492 ☐ test 493 ☐ test 494 ☐ test 495 ☐ test 496 ☐ test 497 ☐ test 498 ☐ test 499 ☐ test 500 ☐ test 501 ☐ test 502 ☐ test 503 Project 504: ☐ test 505 ☐ test 506 ☐ test 507 ☐ test 508 ☐ test 509 ☐ test 510 ☐ test 511 ☐ test 512 ☐ test 513 ☐ test 514 ☐ test 515 ☐ test 516 ☐ test 517 ☐ test 518 Project 519: ☐ test 520 ☐ test 521 ☐ test 522 ☐ test 523 Project 524: ☐ test 525 ☐ test 526 ☐ test 527 ☐ test 528 ☐ test 529 Project 530: ☐ test 531 ☐ test 532 ☐ test 533 ☐ test 534 ☐ test 535 ☐ test 536 ☐ test 537 ☐ test 538 ☐ test 539 ☐ test 540 ☐ test 541 ☐ test 542 ☐ test 543 Project 544: ☐ test 545 Project 546: ☐ test 547 ☐ test 548 ☐ test 549 ☐ test 550 ☐ test 551 ☐ test 552 ☐ test 553 Project 554: Project 555: ☐ test 556 ☐ test 557 ☐ test 558 ☐ test 559 ☐ test 560 ☐ test 561 ☐ test 562 ☐ test 563 Project 564: ☐ test 565 ☐ test 566 ☐ test 567 ☐ test 568 ☐ test 569 ☐ test 570 ☐ test 571 ☐ test 572 ☐ test 573 ☐ test 574 Project 575: Project 576: ☐ test 577 ☐ test 578 ☐ test 579 ☐ test 580 ☐ test 581 ☐ test 582 ☐ test 583 Project 584: ☐ test 585 ☐ test 586 ☐ test 587 ☐ test 588 ☐ test 589 ☐ test 590 ☐ test 591 Project 592: ☐ test 593 ☐ test 594 ☐ test 595 ☐ test 596 ☐ test 597 ☐ test 598 ☐ test 599 ☐ test 600 ☐ test 601 ☐ test 602 Project 603: ☐ test 604 ☐ test 605 ☐ test 606 ☐ test 607 ☐ test 608 ☐ test 609 ☐ test 610 ☐ test 611 ☐ test 612 ☐ test 613 ☐ test 614 ☐ test 615 ☐ test 616 ☐ test 617 ☐ test 618 ☐ test 619 ☐ test 620 ☐ test 621 Project 622: ☐ test 623 ☐ test 624 ☐ test 625 ☐ test 626 ☐ test 627 ☐ test 628 ☐ test 629 ☐ test 630 ☐ test 631 ☐ test 632 ☐ test 633 ☐ test 634 ☐ test 635 ☐ test 636 ☐ test 637 ☐ test 638 ☐ test 639 ☐ test 640 ☐ test 641 ☐ test 642 ☐ test 643 ☐ test 644 ☐ test 645 Project 646: ☐ test 647 ☐ test 648 ☐ test 649 ☐ test 650 ☐ test 651 ☐ test 652 ☐ test 653 ☐ test 654 ☐ test 655 ☐ test 656 ☐ test 657 ☐ test 658 ☐ test 659 Project 660: ☐ test 661 ☐ test 662 ☐ test 663 Project 664: ☐ test 665 ☐ test 666 ☐ test 667 ☐ test 668 ☐ test 669 Project 670: ☐ test 671 ☐ test 672 ☐ test 673 ☐ test 674 ☐ test 675 ☐ test 676 ☐ test 677 ☐ test 678 ☐ test 679 ☐ test 680 ☐ test 681 ☐ test 682 ☐ test 683 ☐ test 684 Project 685: ☐ test 686 ☐ test 687 ☐ test 688 ☐ test 689 ☐ test 690 ☐ test 691 ☐ test 692 ☐ test 693 Project 694: ☐ test 695 ☐ test 696 ☐ test 697 ☐ test 698 ☐ test 699 ☐ test 700 ☐ test 701 ☐ test 702 ☐ test 703 Project 704: ☐ test 705 ☐ test 706 ☐ test 707 ☐ test 708 ☐ test 709 ☐ test 710 ☐ test 711 ☐ test 712 ☐ test 713 ☐ test 714 ☐ test 715 ☐ test 716 ☐ test 717 ☐ test 718 Project 719: ☐ test 720 ☐ test 721 ☐ test 722 Project 723: ☐ test 724 ☐ test 725 ☐ test 726 ☐ test 727 ☐ test 728 ☐ test 729 ☐ test 730 ☐ test 731 ☐ test 732 ☐ test 733 ☐ test 734 ☐ test 735 ☐ test 736 ☐ test 737 ☐ test 738 ☐ test 739 ☐ test 740 ☐ test 741 ☐ test 742 Project 743: ☐ test 744 ☐ test 745 ☐ test 746 ☐ test 747 ☐ test 748 ☐ test 749 ☐ test 750 ☐ test 751 ☐ test 752 ☐ test 753 Project 754: ☐ test 755 ☐ test 756 ☐ test 757 ☐ test 758 ☐ test 759 ☐ test 760 ☐ test 761 ☐ test 762 ☐ test 763 ☐ test 764 ☐ test 765 ☐ test 766 ☐ test 767 ☐ test 768 ☐ test 769 ☐ test 770 ☐ test 771 ☐ test 772 ☐ test 773 ☐ test 774 ☐ test 775 ☐ test 776 ☐ test 777 ☐ test 778 ☐ test 779 ☐ test 780 ☐ test 781 ☐ test 782 Project 783: ☐ test 784 ☐ test 785 ☐ test 786 ☐ test 787 ☐ test 788 ☐ test 789 ☐ test 790 ☐ test 791 ☐ test 792 ☐ test 793 ☐ test 794 ☐ test 795 Project 796: ☐ test 797 ☐ test 798 ☐ test 799 ☐ test 800 ☐ test 801 ☐ test 802 ☐ test 803 ☐ test 804 ☐ test 805 ☐ test 806 ☐ test 807 ☐ test 808 ☐ test 809 ☐ test 810 Project 811: Project 812: Project 813: ☐ test 814 ☐ test 815 Project 816: Project 817: ☐ test 818 ☐ test 819 Project 820: ☐ test 821 ☐ test 822 Project 823: ☐ test 824 ☐ test 825 ☐ test 826 ☐ test 827 ☐ test 828 ☐ test 829 ☐ test 830 ☐ test 831 ☐ test 832 ☐ test 833 ☐ test 834 ☐ test 835 Project 836: Project 837: Project 838: ☐ test 839 ☐ test 840 ☐ test 841 ☐ test 842 ☐ test 843 ☐ test 844 Project 845: ☐ test 846 ☐ test 847 Project 848: ☐ test 849 ☐ test 850 ☐ test 851 ☐ test 852 ☐ test 853 ☐ test 854 ☐ test 855 ☐ test 856 Project 857: ☐ test 858 ☐ test 859 ☐ test 860 ☐ test 861 ☐ test 862 ☐ test 863 ☐ test 864 ☐ test 865 ☐ test 866 ☐ test 867 ☐ test 868 ☐ test 869 ☐ test 870 ☐ test 871 ☐ test 872 ☐ test 873 ☐ test 874 ☐ test 875 ☐ test 876 ☐ test 877 ☐ test 878 ☐ test 879 ☐ test 880 Project 881: ☐ test 882 ☐ test 883 ☐ test 884 ☐ test 885 ☐ test 886 ☐ test 887 ☐ test 888 ☐ test 889 Project 890: ☐ test 891 ☐ test 892 ☐ test 893 ☐ test 894 ☐ test 895 ☐ test 896 ☐ test 897 ☐ test 898 ☐ test 899 ☐ test 900 ☐ test 901 ☐ test 902 ☐ test 903 ☐ test 904 ☐ test 905 ☐ test 906 ☐ test 907 ☐ test 908 ☐ test 909 ☐ test 910 ☐ test 911 ☐ test 912 ☐ test 913 ☐ test 914 ☐ test 915 ☐ test 916 ☐ test 917 ☐ test 918 ☐ test 919 Project 920: ☐ test 921 ☐ test 922 ☐ test 923 ☐ test 924 ☐ test 925 ☐ test 926 ☐ test 927 ☐ test 928 ☐ test 929 ☐ test 930 ☐ test 931 Project 932: ☐ test 933 ☐ test 934 ☐ test 935 ☐ test 936 ☐ test 937 ☐ test 938 ☐ test 939 ☐ test 940 ☐ test 941 ☐ test 942 ☐ test 943 ☐ test 944 ☐ test 945 ☐ test 946 ☐ test 947 ☐ test 948 ☐ test 949 ☐ test 950 ☐ test 951 ☐ test 952 ☐ test 953 ☐ test 954 ☐ test 955 ☐ test 956 ☐ test 957 ☐ test 958 Project 959: ☐ test 960 ☐ test 961 ☐ test 962 ☐ test 963 ☐ test 964 ☐ test 965 ☐ test 966 ☐ test 967 ☐ test 968 ☐ test 969 ☐ test 970 Project 971: ☐ test 972 ☐ test 973 ☐ test 974 ☐ test 975 ☐ test 976 ☐ test 977 ☐ test 978 ☐ test 979 ☐ test 980 ☐ test 981 ☐ test 982 ☐ test 983 Project 984: ☐ test 985 ☐ test 986 ☐ test 987 ☐ test 988 ☐ test 989 ☐ test 990 ☐ test 991 ☐ test 992 Project 993: ☐ test 994 ☐ test 995 ☐ test 996 ☐ test 997 ☐ test 998 ☐ test 999 ☐ test 1000 ================================================ FILE: spec/complexMarkers.todo ================================================ Test: [ ] First task @tag1 [ ] Second task plain text, non-task [ ] Another task ================================================ FILE: spec/sample.taskpaper ================================================ Yard: - Rake leaves - Put away hose @tag1 and something else @tag2 - Clear garden Groceries: - Milk - Eggs - Lettuce ================================================ FILE: spec/tasks-spec.coffee ================================================ path = require 'path' Tasks = require '../lib/tasks' tasksUtilities = require '../lib/tasksUtilities' [editor, buffer, grammar, workspaceElement] = [] baseTokens = ['source.todo', 'tasks.text'] doneTokens = ['source.todo', 'tasks.text.done'] cancelledTokens = ['source.todo', 'tasks.text.cancelled'] nowStamp = '' describe 'Tasks', -> beforeEach -> waitsForPromise -> atom.workspace.open().then (o) -> editor = o waitsForPromise -> atom.packages.activatePackage('tasks') runs -> grammar = atom.grammars.grammarForScopeName 'source.todo' editor = atom.workspace.getActiveTextEditor() editor.setGrammar grammar nowStamp = tasksUtilities.getFormattedDate(1000) describe 'grammar should load', -> it 'loads', -> expect(grammar).toBeDefined() expect(grammar.scopeName).toBe 'source.todo' describe 'should tokenize', -> it 'tokenizes a task', -> tokens = grammar.tokenizeLines('☐ text @tag(test)') expect(tokens[0][0]).toEqual value: '☐', scopes: [baseTokens..., 'keyword.tasks.marker'] expect(tokens[0][1]).toEqual value: ' text ', scopes: baseTokens expect(tokens[0][2]).toEqual value: '@', scopes: [baseTokens..., 'tasks.attribute.tag'] expect(tokens[0][3]).toEqual value: 'tag', scopes: [baseTokens..., 'tasks.attribute.tag', 'tasks.attribute-name'] # skip ( expect(tokens[0][5]).toEqual value: 'test', scopes: [baseTokens..., 'tasks.attribute.tag', 'tasks.attribute-value'] it 'tokenizes a project', -> tokens = grammar.tokenizeLines('project:') expect(tokens[0][0]).toEqual value: 'project', scopes: ['source.todo', 'control.tasks.header.project', 'control.tasks.header-title'] it 'tokenizes a completed task', -> tokens = grammar.tokenizeLines('✔ text @done()') expect(tokens[0][3]).toEqual value: 'done', scopes: [doneTokens..., 'tasks.attribute.done', 'tasks.attribute-name'] it 'tokenizes a cancelled task', -> tokens = grammar.tokenizeLines('✘ text @cancelled()') expect(tokens[0][3]).toEqual value: 'cancelled', scopes: [cancelledTokens..., 'tasks.attribute.cancelled', 'tasks.attribute-name'] describe 'manage tasks', -> it 'creates a task below', -> editor.setText '☐ item 1' Tasks.newTask() editor.insertText 'item 2' line = editor.tokenizedBuffer.tokenizedLines[1] expect(line.tokens[0]).toEqual value: '☐', scopes: [baseTokens..., 'keyword.tasks.marker'] expect(editor.indentationForBufferRow 1).toBe 0 it 'creates a task above', -> editor.setText '☐ item 1' Tasks.newTask(-1) editor.insertText 'item 2' line = editor.tokenizedBuffer.tokenizedLines[0] expect(line.tokens[0]).toEqual value: '☐', scopes: [baseTokens..., 'keyword.tasks.marker'] expect(editor.indentationForBufferRow 1).toBe 0 it 'completes tasks', -> editor.setText 'project:\n ☐ item 1' editor.setCursorBufferPosition [1,0] Tasks.completeTask() line = editor.tokenizedBuffer.tokenizedLines[1] expect(line.tokens[1]).toEqual value: '✔', scopes: [doneTokens..., 'keyword.tasks.marker'] expect(line.tokens[4]).toEqual value: 'done', scopes: [doneTokens..., 'tasks.attribute.done', 'tasks.attribute-name'] expect(line.tokens[12]).toEqual value: 'project', scopes: [doneTokens..., 'tasks.attribute.project', 'tasks.attribute-value'] it 'cancels tasks', -> editor.setText 'project:\n ☐ item 1' editor.setCursorBufferPosition [1,0] Tasks.cancelTask() line = editor.tokenizedBuffer.tokenizedLines[1] expect(line.tokens[1]).toEqual value: '✘', scopes: [cancelledTokens..., 'keyword.tasks.marker'] expect(line.tokens[4]).toEqual value: 'cancelled', scopes: [cancelledTokens..., 'tasks.attribute.cancelled', 'tasks.attribute-name'] expect(line.tokens[12]).toEqual value: 'project', scopes: [cancelledTokens..., 'tasks.attribute.project', 'tasks.attribute-value'] it 'should add a timestamp', -> editor.setText ' ☐ item 1' Tasks.setTimestamp() line = editor.tokenizedBuffer.tokenizedLines[0] expect(line.tokens[4]).toEqual value: 'timestamp', scopes: [baseTokens..., 'tasks.attribute.timestamp', 'tasks.attribute-name'] expect(line.tokens[6]).toEqual value: nowStamp, scopes: [baseTokens..., 'tasks.attribute.timestamp', 'tasks.attribute-value'] it 'should update a timestamp', -> editor.setText ' ✔ item 1 @done(1970-1-1 0:00)' Tasks.setTimestamp() line = editor.tokenizedBuffer.tokenizedLines[0] expect(line.tokens[6]).toEqual value: nowStamp, scopes: [doneTokens..., 'tasks.attribute.done', 'tasks.attribute-value'] it 'should convert to task', -> editor.setText 'item' Tasks.convertToTask() line = editor.tokenizedBuffer.tokenizedLines[0] expect(line.tokens[0]).toEqual value: '☐', scopes: [baseTokens..., 'keyword.tasks.marker'] it 'should convert to task with timestamp', -> atom.config.set 'tasks.addTimestampOnConvertToTask', true editor.setText 'item' Tasks.convertToTask() line = editor.tokenizedBuffer.tokenizedLines[0] expect(line.tokens[5]).toEqual value: nowStamp, scopes: [baseTokens..., 'tasks.attribute.timestamp', 'tasks.attribute-value'] it 'should archive completed tasks', -> editor.setText(''' project: ☐ item 1 ✔ item 2 @done(1970-1-1 0:00) @project(project) ''') Tasks.tasksArchive() line = editor.tokenizedBuffer.tokenizedLines[3] expect(line.tokens[0]).toEqual value: '___________________', scopes: ['source.todo'] line = editor.tokenizedBuffer.tokenizedLines[4] expect(line.tokens[0]).toEqual value: 'Archive', scopes: ['source.todo', 'control.tasks.header.archive', 'control.tasks.header-title'] line = editor.tokenizedBuffer.tokenizedLines[5] expect(line.tokens[2]).toEqual value: ' item 2 ', scopes: doneTokens it 'should archive cancelled tasks', -> editor.setText(''' project: ☐ item 1 ✘ item 2 @cancelled(1970-1-1 0:00) @project(project) ''') Tasks.tasksArchive() line = editor.tokenizedBuffer.tokenizedLines[5] expect(line.tokens[2]).toEqual value: ' item 2 ', scopes: cancelledTokens describe 'Taskpaper', -> beforeEach -> atom.config.set 'tasks.baseMarker', '-' atom.config.set 'tasks.completeMarker', '-' atom.config.set 'tasks.cancelledMarker', '-' grammar = atom.grammars.grammarForScopeName 'source.todo' editor.setGrammar grammar describe 'should tokenize', -> it 'tokenizes a task', -> tokens = grammar.tokenizeLines('- text @tag(test)') expect(tokens[0][0]).toEqual value: '-', scopes: [baseTokens..., 'keyword.tasks.marker'] expect(tokens[0][1]).toEqual value: ' text ', scopes: baseTokens expect(tokens[0][2]).toEqual value: '@', scopes: [baseTokens..., 'tasks.attribute.tag'] expect(tokens[0][3]).toEqual value: 'tag', scopes: [baseTokens..., 'tasks.attribute.tag', 'tasks.attribute-name'] # skip ( expect(tokens[0][5]).toEqual value: 'test', scopes: [baseTokens..., 'tasks.attribute.tag', 'tasks.attribute-value'] it 'tokenizes a completed task', -> tokens = grammar.tokenizeLines('- text @done()') expect(tokens[0][3]).toEqual value: 'done', scopes: [doneTokens..., 'tasks.attribute.done', 'tasks.attribute-name'] it 'tokenizes a cancelled task', -> tokens = grammar.tokenizeLines('- text @cancelled()') expect(tokens[0][3]).toEqual value: 'cancelled', scopes: [cancelledTokens..., 'tasks.attribute.cancelled', 'tasks.attribute-name'] describe 'Complex markers', -> beforeEach -> atom.config.set 'tasks.baseMarker', '[ ]' atom.config.set 'tasks.completeMarker', '[x]' atom.config.set 'tasks.cancelledMarker', '[-]' grammar = atom.grammars.grammarForScopeName 'source.todo' editor.setGrammar grammar describe 'should tokenize', -> it 'tokenizes a task', -> tokens = grammar.tokenizeLines('[ ] text @tag(test)') expect(tokens[0][0]).toEqual value: '[ ]', scopes: [baseTokens..., 'keyword.tasks.marker'] expect(tokens[0][1]).toEqual value: ' text ', scopes: baseTokens expect(tokens[0][2]).toEqual value: '@', scopes: [baseTokens..., 'tasks.attribute.tag'] expect(tokens[0][3]).toEqual value: 'tag', scopes: [baseTokens..., 'tasks.attribute.tag', 'tasks.attribute-name'] # skip ( expect(tokens[0][5]).toEqual value: 'test', scopes: [baseTokens..., 'tasks.attribute.tag', 'tasks.attribute-value'] it 'tokenizes a completed task', -> tokens = grammar.tokenizeLines('[x] text @done()') expect(tokens[0][3]).toEqual value: 'done', scopes: [doneTokens..., 'tasks.attribute.done', 'tasks.attribute-name'] it 'tokenizes a cancelled task', -> tokens = grammar.tokenizeLines('[-] text @cancelled()') expect(tokens[0][3]).toEqual value: 'cancelled', scopes: [cancelledTokens..., 'tasks.attribute.cancelled', 'tasks.attribute-name'] describe 'Escape', -> beforeEach -> editor.setText 'Project with (parens):\n ☐ An incomplete task' editor.setCursorBufferPosition [1,0] describe 'should escape tag values', -> it 'adds a project attribute', -> Tasks.completeTask() projectTag = tasksUtilities.getTag editor, 1, 'project', '@' expect(projectTag).toBeDefined() expect(projectTag.tagValue.value).toEqual("Project with \\(parens\\)") describe 'should discard parenthesized done tags', -> it 'adds a project attribute', -> Tasks.completeTask() projectTag = tasksUtilities.getTag editor, 1, 'project', '@' # undo completion Tasks.completeTask() line = editor.getBuffer().lineForRow 1 expect(line).toMatch(/☐\s+An incomplete task\s*$/) ================================================ FILE: styles/tasks.less ================================================ @import "ui-variables"; atom-text-editor.editor { .syntax--tasks { &.syntax--header { color: @text-color-warning; } &.syntax--marker { color: @text-color-subtle; } &.syntax--attribute { color: @text-color-info; } &.syntax--done, &.syntax--cancelled { &.syntax--text { color: @text-color-subtle; } .syntax--marker { color: @text-color-success; } .syntax--attribute { color: mix(@text-color-info, @text-color-subtle, 30%); } } &.syntax--cancelled .syntax--marker { color: @text-color-error; } } }