├── LICENSE.md ├── README.md ├── keymaps └── show-todo.cson ├── lib ├── main.js ├── todo-collection.coffee ├── todo-indicator-view.coffee ├── todo-item-view.coffee ├── todo-markdown.coffee ├── todo-model.coffee ├── todo-options-view.coffee ├── todo-regex.coffee ├── todo-show.js ├── todo-table-view.coffee └── todo-view.coffee ├── menus └── show-todo.cson ├── package.json └── styles └── show-todo.less /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Martin Rodalgaard 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Todo Show Package [![Build Status](https://travis-ci.org/mrodalgaard/atom-todo-show.svg)](https://travis-ci.org/mrodalgaard/atom-todo-show) 2 | 3 | > DEPRECATED: This package will no longer be maintained by me since I have switched permanently to VSCode. 4 | 5 | Finds all TODO, FIXME, CHANGED, XXX, IDEA, HACK, NOTE, REVIEW, NB, BUG, QUESTION, COMBAK, TEMP comments in your project and shows them in a nice overview list. 6 | 7 | Attention: This package searches for todos. For todo word highlighting see [language-todo](https://github.com/atom/language-todo). 8 | 9 | Open todo list using command palette `Todo Show: Find In Workspace`, `Todo Show: Find In Project` or `Todo Show: Find In Open Files`. Keyboard shortcuts CTRL + SHIFT + T on Mac OSX or ALT + SHIFT + T on Windows and Linux. 10 | 11 | Install with `apm install todo-show` or use *Install Packages* from *Atom Settings*. 12 | 13 | ![todo-show-package](https://raw.githubusercontent.com/mrodalgaard/atom-todo-show/master/screenshots/preview.png) 14 | 15 | ## Search Scopes 16 | 17 | Five different scopes are available to narrow down your search for todos. Change scope using the button at the top of the todo view or use the corresponding command. 18 | * __Workspace__: Searches all open projects. 19 | * __Project__: Searches active project (a project is marked as active when you open a file it contains). 20 | * __Open Files__: Searches currently open files. 21 | * __Active File__: Searches last active file only. 22 | * __Custom__: Tree view has a context menu to search for todos in the selected folder or file. 23 | 24 | ## Configuration 25 | 26 | Name | Default | Description 27 | ----------------------|-----------------------------------------|------------ 28 | _autoRefresh_ | `true` | Automatic refresh of todo list after saving 29 | _findTheseTodos_ | `['FIXME', 'TODO', 'CHANGED', 'XXX', 'IDEA', 'HACK', 'NOTE', 'REVIEW', 'NB', 'BUG', 'QUESTION', 'COMBAK', 'TEMP']` | An array of todo types used by the search regex 30 | _findUsingRegex_ | See 'Regular Expression Search' section | Regex string used to find all your todos. `${TODOS}` is replaced with `FindTheseTodos` from above 31 | _ignoreThesePaths_ | `['node_modules', 'vendor', 'bower_components', '*.pdf']` | An array of files / folders to exclude (syntax according to [scandal](https://github.com/atom/scandal) used internally by Atom).
⚬ _globally_: `Ignored Names` from atom core settings.
⚬ _locally_: Ignores anything in your `.gitignore` file, if the current project is a valid git repository and atom core setting `Exclude VCS Ignored Paths` is checked. 32 | _showInTable_ | `['Text', 'Type', 'Path']` | An array of properties to show for each todo in table 33 | _sortBy_ | `'Text'` | Sort table by this todo property 34 | _sortAscending_ | `true` | Sort table in ascending or descending order 35 | _exportAs_ | `'List'` | Choose which format to use for exported markdown 36 | _statusBarIndicator_ | `false` | Show todo count in status bar (this is only shown and updated when the 'Todo Show' tab is open) 37 | 38 | ## Regular Expression Search 39 | 40 | The regexes in `findTheseRegexes` are used for searching the workspace for todo matches. They are configurable to match the users specific needs. 41 | 42 | Default regex string: `'/\\b(${TODOS})[:;.,]?\\d*($|\\s.*$|\\(.*$)/g'` 43 | * `\b` start at word boundary 44 | * `${TODOS}` todo type match (is replaced with `findTheseTodos`) 45 | * `[:;.,]?` optional separator after type 46 | * `\d*` optional digits for supporting [imdone](http://imdone.io/) sorting 47 | * `$` to end todos without additional text (newline) 48 | * Or `\s.*$` to match the todo text with a non-optional space in front 49 | * Or an immediate parentheses, `\(.*$`, to support [Google style guide IDs](https://google.github.io/styleguide/cppguide.html#TODO_Comments) 50 | * Because Atom config only accepts strings all `\` characters are also escaped 51 | 52 | To extend the default todo types and search regex, the existing config needs to be copied into your `config.cson`. 53 | 54 | ## Other Features 55 | 56 | * Configurable Columns: You can define which columns you want to see in your todo list: Text, Type, Path, Tags, Range, Line, Regex, File, Id, Project 57 | * Sortable Columns: All columns in the todo list can be sorted ascending or descending. 58 | * TODO tags: Todos can be tagged using hashtags (e.g. `TODO: do this #object #profile`), which are presented in the "Tags" column. 59 | * Live search: You can search within your todo list. Note: This feature is temporarily disabled due to a bug, see https://github.com/mrodalgaard/atom-todo-show/issues/198 60 | 61 | ## Credits 62 | Originally created by [Jamis Charles](https://github.com/jamischarles) 63 | 64 | Now maintained by [Martin Rodalgaard](https://github.com/mrodalgaard) 65 | -------------------------------------------------------------------------------- /keymaps/show-todo.cson: -------------------------------------------------------------------------------- 1 | '.platform-darwin atom-workspace': 2 | 'ctrl-shift-t': 'todo-show:toggle' 3 | 4 | '.platform-win32 atom-workspace, .platform-linux atom-workspace': 5 | 'alt-shift-t': 'todo-show:toggle' 6 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | const TodoShow = require('./todo-show') 2 | 3 | module.exports = new TodoShow() 4 | -------------------------------------------------------------------------------- /lib/todo-collection.coffee: -------------------------------------------------------------------------------- 1 | path = require 'path' 2 | {Emitter} = require 'atom' 3 | 4 | TodoModel = require './todo-model' 5 | TodosMarkdown = require './todo-markdown' 6 | TodoRegex = require './todo-regex' 7 | 8 | module.exports = 9 | class TodoCollection 10 | constructor: -> 11 | @emitter = new Emitter 12 | @defaultKey = 'Text' 13 | @scope = 'workspace' 14 | @todos = [] 15 | 16 | onDidAddTodo: (cb) -> @emitter.on 'did-add-todo', cb 17 | onDidRemoveTodo: (cb) -> @emitter.on 'did-remove-todo', cb 18 | onDidClear: (cb) -> @emitter.on 'did-clear-todos', cb 19 | onDidStartSearch: (cb) -> @emitter.on 'did-start-search', cb 20 | onDidSearchPaths: (cb) -> @emitter.on 'did-search-paths', cb 21 | onDidFinishSearch: (cb) -> @emitter.on 'did-finish-search', cb 22 | onDidCancelSearch: (cb) -> @emitter.on 'did-cancel-search', cb 23 | onDidFailSearch: (cb) -> @emitter.on 'did-fail-search', cb 24 | onDidSortTodos: (cb) -> @emitter.on 'did-sort-todos', cb 25 | onDidFilterTodos: (cb) -> @emitter.on 'did-filter-todos', cb 26 | onDidChangeSearchScope: (cb) -> @emitter.on 'did-change-scope', cb 27 | 28 | clear: -> 29 | @cancelSearch() 30 | @todos = [] 31 | @emitter.emit 'did-clear-todos' 32 | 33 | addTodo: (todo) -> 34 | return if @alreadyExists(todo) 35 | @todos.push(todo) 36 | @emitter.emit 'did-add-todo', todo 37 | 38 | getTodos: -> @todos 39 | getTodosCount: -> @todos.length 40 | getState: -> @searching 41 | 42 | sortTodos: ({sortBy, sortAsc} = {}) -> 43 | sortBy ?= @defaultKey 44 | 45 | # Save history of new sort elements 46 | if @searches?[@searches.length - 1].sortBy isnt sortBy 47 | @searches ?= [] 48 | @searches.push {sortBy, sortAsc} 49 | else 50 | @searches[@searches.length - 1] = {sortBy, sortAsc} 51 | 52 | @todos = @todos.sort((todoA, todoB) => 53 | @todoSorter(todoA, todoB, sortBy, sortAsc) 54 | ) 55 | 56 | return @filterTodos(@filter) if @filter 57 | @emitter.emit 'did-sort-todos', @todos 58 | 59 | todoSorter: (todoA, todoB, sortBy, sortAsc) -> 60 | [sortBy2, sortAsc2] = [sortBy, sortAsc] 61 | 62 | aVal = todoA.get(sortBy2) 63 | bVal = todoB.get(sortBy2) 64 | 65 | if aVal is bVal 66 | # Use previous sorts to make a 2-level stable sort 67 | if search = @searches?[@searches.length - 2] 68 | [sortBy2, sortAsc2] = [search.sortBy, search.sortAsc] 69 | else 70 | sortBy2 = @defaultKey 71 | 72 | [aVal, bVal] = [todoA.get(sortBy2), todoB.get(sortBy2)] 73 | 74 | # Sort type in the defined order, as number or normal string sort 75 | if sortBy2 is 'Type' 76 | findTheseTodos = atom.config.get('todo-show.findTheseTodos') 77 | comp = findTheseTodos.indexOf(aVal) - findTheseTodos.indexOf(bVal) 78 | else if todoA.keyIsNumber(sortBy2) 79 | comp = parseInt(aVal) - parseInt(bVal) 80 | else 81 | comp = aVal.localeCompare(bVal) 82 | if sortAsc2 then comp else -comp 83 | 84 | filterTodos: (filter) -> 85 | @filter = filter 86 | @emitter.emit 'did-filter-todos', @getFilteredTodos() 87 | 88 | getFilteredTodos: -> 89 | return @todos unless filter = @filter 90 | @todos.filter (todo) -> 91 | todo.contains(filter) 92 | 93 | getAvailableTableItems: -> @availableItems 94 | setAvailableTableItems: (@availableItems) -> 95 | 96 | getSearchScope: -> @scope 97 | setSearchScope: (scope) -> 98 | @emitter.emit 'did-change-scope', @scope = scope 99 | 100 | toggleSearchScope: -> 101 | scope = switch @scope 102 | when 'workspace' then 'project' 103 | when 'project' then 'open' 104 | when 'open' then 'active' 105 | else 'workspace' 106 | @setSearchScope(scope) 107 | scope 108 | 109 | getCustomPath: -> @customPath 110 | setCustomPath: (@customPath) -> 111 | 112 | alreadyExists: (newTodo) -> 113 | properties = ['range', 'path'] 114 | @todos.some (todo) -> 115 | properties.every (prop) -> 116 | true if todo[prop] is newTodo[prop] 117 | 118 | # Scan project workspace for the TodoRegex object 119 | # returns a promise that the scan generates 120 | fetchRegexItem: (todoRegex, activeProjectOnly) -> 121 | options = 122 | paths: @getSearchPaths() 123 | onPathsSearched: (nPaths) => 124 | @emitter.emit 'did-search-paths', nPaths if @searching 125 | 126 | atom.workspace.scan todoRegex.regexp, options, (result, error) => 127 | console.debug error.message if error 128 | return unless result 129 | 130 | return if activeProjectOnly and not @activeProjectHas(result.filePath) 131 | 132 | for match in result.matches 133 | @addTodo new TodoModel( 134 | all: match.lineText 135 | text: match.matchText 136 | loc: result.filePath 137 | position: match.range 138 | regex: todoRegex.regex 139 | regexp: todoRegex.regexp 140 | ) 141 | 142 | # Scan open files for the TodoRegex object 143 | fetchOpenRegexItem: (todoRegex, activeEditorOnly) -> 144 | editors = [] 145 | if activeEditorOnly 146 | if editor = atom.workspace.getActiveTextEditor() 147 | editors = [editor] 148 | else 149 | editors = atom.workspace.getTextEditors() 150 | 151 | for editor in editors 152 | editor.scan todoRegex.regexp, (match, error) => 153 | console.debug error.message if error 154 | return unless match 155 | 156 | range = [ 157 | [match.range.start.row, match.range.start.column] 158 | [match.range.end.row, match.range.end.column] 159 | ] 160 | 161 | @addTodo new TodoModel( 162 | all: match.lineText 163 | text: match.matchText 164 | loc: editor.getPath() 165 | position: range 166 | regex: todoRegex.regex 167 | regexp: todoRegex.regexp 168 | ) 169 | 170 | # No async operations, so just return a resolved promise 171 | Promise.resolve() 172 | 173 | search: (force = false) -> 174 | return if !atom.config.get('todo-show.autoRefresh') and !force 175 | 176 | @clear() 177 | @searching = true 178 | @emitter.emit 'did-start-search' 179 | 180 | todoRegex = new TodoRegex( 181 | atom.config.get('todo-show.findUsingRegex') 182 | atom.config.get('todo-show.findTheseTodos') 183 | ) 184 | 185 | if todoRegex.error 186 | @emitter.emit 'did-fail-search', "Invalid todo search regex" 187 | return 188 | 189 | @searchPromise = switch @scope 190 | when 'open' then @fetchOpenRegexItem(todoRegex, false) 191 | when 'active' then @fetchOpenRegexItem(todoRegex, true) 192 | when 'project' then @fetchRegexItem(todoRegex, true) 193 | else @fetchRegexItem(todoRegex) 194 | 195 | @searchPromise.then (result) => 196 | @searching = false 197 | if result is 'cancelled' 198 | @emitter.emit 'did-cancel-search' 199 | else 200 | @emitter.emit 'did-finish-search' 201 | .catch (reason) => 202 | @searching = false 203 | @emitter.emit 'did-fail-search', reason 204 | 205 | getSearchPaths: -> 206 | return [@getCustomPath()] if @scope is 'custom' 207 | 208 | ignores = atom.config.get('todo-show.ignoreThesePaths') 209 | return ['*'] unless ignores? 210 | if Object.prototype.toString.call(ignores) isnt '[object Array]' 211 | @emitter.emit 'did-fail-search', "ignoreThesePaths must be an array" 212 | return ['*'] 213 | "!#{ignore}" for ignore in ignores 214 | 215 | activeProjectHas: (filePath = '') -> 216 | return unless project = @getActiveProject() 217 | filePath.indexOf(project) is 0 218 | 219 | getActiveProject: -> 220 | return @activeProject if @activeProject 221 | @activeProject = project if project = @getFallbackProject() 222 | 223 | getFallbackProject: -> 224 | for item in atom.workspace.getPaneItems() 225 | if project = @projectForFile(item.getPath?()) 226 | return project 227 | project if project = atom.project.getPaths()[0] 228 | 229 | getActiveProjectName: -> 230 | return 'no active project' unless project = @getActiveProject() 231 | projectName = path.basename(project) 232 | if projectName is 'undefined' then "no active project" else projectName 233 | 234 | setActiveProject: (filePath) -> 235 | lastProject = @activeProject 236 | @activeProject = project if project = @projectForFile(filePath) 237 | return false unless lastProject 238 | lastProject isnt @activeProject 239 | 240 | projectForFile: (filePath) -> 241 | return if typeof filePath isnt 'string' 242 | project if project = atom.project.relativizePath(filePath)[0] 243 | 244 | getMarkdown: -> 245 | todosMarkdown = new TodosMarkdown 246 | todosMarkdown.markdown @getFilteredTodos() 247 | 248 | cancelSearch: -> 249 | @searchPromise?.cancel?() 250 | 251 | # TODO: Previous searches are not saved yet! 252 | getPreviousSearch: -> 253 | sortBy = localStorage.getItem 'todo-show.previous-sortBy' 254 | 255 | setPreviousSearch: (search) -> 256 | localStorage.setItem 'todo-show.previous-search', search 257 | -------------------------------------------------------------------------------- /lib/todo-indicator-view.coffee: -------------------------------------------------------------------------------- 1 | {View} = require 'atom-space-pen-views' 2 | {CompositeDisposable} = require 'atom' 3 | 4 | module.exports = 5 | class TabNumbersView extends View 6 | nTodos: 0 7 | 8 | @content: -> 9 | @div class: 'todo-status-bar-indicator inline-block', tabindex: -1, => 10 | @a class: 'inline-block', => 11 | @span class: 'icon icon-checklist' 12 | @span outlet: 'todoCount' 13 | 14 | initialize: (@collection) -> 15 | @disposables = new CompositeDisposable 16 | @on 'click', this.element, @activateTodoPackage 17 | 18 | @update() 19 | @disposables.add @collection.onDidFinishSearch @update 20 | 21 | destroy: -> 22 | @disposables.dispose() 23 | @detach() 24 | 25 | update: => 26 | @nTodos = @collection.getTodosCount() 27 | @todoCount.text(@nTodos) 28 | 29 | @toolTipDisposable?.dispose() 30 | @toolTipDisposable = atom.tooltips.add @element, title: "#{@nTodos} TODOs" 31 | 32 | activateTodoPackage: -> 33 | atom.commands.dispatch(this, 'todo-show:toggle') 34 | -------------------------------------------------------------------------------- /lib/todo-item-view.coffee: -------------------------------------------------------------------------------- 1 | {View} = require 'atom-space-pen-views' 2 | 3 | class TableHeaderView extends View 4 | @content: (showInTable = [], {sortBy, sortAsc}) -> 5 | @tr => 6 | for item in showInTable 7 | @th item, => 8 | if item is sortBy and sortAsc 9 | @div class: 'sort-asc icon-triangle-down active' 10 | else 11 | @div class: 'sort-asc icon-triangle-down' 12 | if item is sortBy and not sortAsc 13 | @div class: 'sort-desc icon-triangle-up active' 14 | else 15 | @div class: 'sort-desc icon-triangle-up' 16 | 17 | class TodoView extends View 18 | @content: (showInTable = [], todo) -> 19 | @tr => 20 | for item in showInTable 21 | @td => 22 | switch item 23 | when 'All' then @span todo.all 24 | when 'Text' then @span todo.text 25 | when 'Type' then @i todo.type 26 | when 'Range' then @i todo.range 27 | when 'Line' then @i todo.line 28 | when 'Regex' then @code todo.regex 29 | when 'Path' then @a todo.path 30 | when 'File' then @a todo.file 31 | when 'Tags' then @i todo.tags 32 | when 'Id' then @i todo.id 33 | when 'Project' then @a todo.project 34 | 35 | initialize: (showInTable, @todo) -> 36 | @handleEvents() 37 | 38 | destroy: -> 39 | @detach() 40 | 41 | handleEvents: -> 42 | @on 'click', 'td', @openPath 43 | 44 | openPath: => 45 | return unless @todo and @todo.loc 46 | position = [@todo.position[0][0], @todo.position[0][1]] 47 | 48 | atom.workspace.open(@todo.loc, { 49 | pending: atom.config.get('core.allowPendingPaneItems') or false 50 | }).then -> 51 | # Setting initialColumn/Line does not always center view 52 | if textEditor = atom.workspace.getActiveTextEditor() 53 | textEditor.setCursorBufferPosition(position, autoscroll: false) 54 | textEditor.scrollToCursorPosition(center: true) 55 | 56 | class TodoEmptyView extends View 57 | @content: (showInTable = []) -> 58 | @tr => 59 | @td colspan: showInTable.length, => 60 | @p "No results..." 61 | 62 | module.exports = {TableHeaderView, TodoView, TodoEmptyView} 63 | -------------------------------------------------------------------------------- /lib/todo-markdown.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | class TodosMarkdown 3 | constructor: -> 4 | @showInTable = atom.config.get('todo-show.showInTable') 5 | 6 | getTable: (todos) -> 7 | md = "| #{(for key in @showInTable then key).join(' | ')} |\n" 8 | md += "|#{Array(md.length-2).join('-')}|\n" 9 | md + (for todo in todos 10 | out = '|' + todo.getMarkdownArray(@showInTable).join(' |') 11 | "#{out} |\n" 12 | ).join('') 13 | 14 | getList: (todos) -> 15 | (for todo in todos 16 | out = '-' + todo.getMarkdownArray(@showInTable).join('') 17 | out = "- No details" if out is '-' 18 | "#{out}\n" 19 | ).join('') 20 | 21 | markdown: (todos) -> 22 | if atom.config.get('todo-show.exportAs') is 'Table' 23 | @getTable todos 24 | else 25 | @getList todos 26 | -------------------------------------------------------------------------------- /lib/todo-model.coffee: -------------------------------------------------------------------------------- 1 | path = require 'path' 2 | 3 | {Emitter} = require 'atom' 4 | _ = require 'underscore-plus' 5 | 6 | maxLength = 120 7 | 8 | module.exports = 9 | class TodoModel 10 | constructor: (match, {plain} = []) -> 11 | return _.extend(this, match) if plain 12 | @handleScanMatch match 13 | 14 | getAllKeys: -> 15 | atom.config.get('todo-show.showInTable') or ['Text'] 16 | 17 | get: (key = '') -> 18 | return value if (value = @[key.toLowerCase()]) or value is '' 19 | @text or 'No details' 20 | 21 | getMarkdown: (key = '') -> 22 | return '' unless value = @[key.toLowerCase()] 23 | switch key 24 | when 'All', 'Text' then " #{value}" 25 | when 'Type', 'Project' then " __#{value}__" 26 | when 'Range', 'Line' then " _:#{value}_" 27 | when 'Regex' then " _'#{value}'_" 28 | when 'Path', 'File' then " [#{value}](#{value})" 29 | when 'Tags', 'Id' then " _#{value}_" 30 | 31 | getMarkdownArray: (keys) -> 32 | for key in keys or @getAllKeys() 33 | @getMarkdown(key) 34 | 35 | keyIsNumber: (key) -> 36 | key in ['Range', 'Line'] 37 | 38 | contains: (string = '') -> 39 | for key in @getAllKeys() 40 | break unless item = @get(key) 41 | return true if item.toLowerCase().indexOf(string.toLowerCase()) isnt -1 42 | false 43 | 44 | handleScanMatch: (match) -> 45 | matchText = match.text or match.all or '' 46 | if matchText.length > match.all?.length 47 | match.all = matchText 48 | 49 | # Strip out the regex token from the found annotation 50 | # not all objects will have an exec match 51 | while (_matchText = match.regexp?.exec(matchText)) 52 | # Find match type 53 | match.type = _matchText[1] unless match.type 54 | # Extract todo text 55 | matchText = _matchText.pop() 56 | 57 | # Extract google style guide todo id 58 | if matchText.indexOf('(') is 0 59 | if matches = matchText.match(/\((.*?)\):?(.*)/) 60 | matchText = matches.pop() 61 | match.id = matches.pop() 62 | 63 | matchText = @stripCommentEnd(matchText) 64 | 65 | # Extract todo tags 66 | match.tags = (while (tag = /\s*#([\w.|]+)[,.]?$/.exec(matchText)) 67 | break if tag.length isnt 2 68 | matchText = matchText.slice(0, -tag.shift().length) 69 | tag.shift().trim().replace(/[\.,]\s*$/, '') 70 | ).sort().join(', ') 71 | 72 | # Use text before todo if no content after 73 | if not matchText and match.all and pos = match.position?[0]?[1] 74 | matchText = match.all.substr(0, pos) 75 | matchText = @stripCommentStart(matchText) 76 | 77 | # Truncate long match strings 78 | if matchText.length >= maxLength 79 | matchText = "#{matchText.substr(0, maxLength - 3)}..." 80 | 81 | # Make sure range is serialized to produce correct rendered format 82 | match.position = [[0,0]] unless match.position and match.position.length > 0 83 | if match.position.serialize 84 | match.range = match.position.serialize().toString() 85 | else 86 | match.range = match.position.toString() 87 | 88 | # Extract paths and project 89 | relativePath = atom.project.relativizePath(match.loc) 90 | relativePath[0] ?= '' 91 | match.path = relativePath[1] or '' 92 | 93 | if (match.loc and loc = path.basename(match.loc)) isnt 'undefined' 94 | match.file = loc 95 | else 96 | match.file = 'untitled' 97 | 98 | if (project = path.basename(relativePath[0])) isnt 'null' 99 | match.project = project 100 | else 101 | match.project = '' 102 | 103 | match.text = matchText or "No details" 104 | match.line = (parseInt(match.range.split(',')[0]) + 1).toString() 105 | match.regex = match.regex.replace('${TODOS}', match.type) 106 | match.id = match.id or '' 107 | 108 | _.extend(this, match) 109 | 110 | stripCommentStart: (text = '') -> 111 | startRegex = /(\/\*|<\?||#>|-}|\]\])\s*$/ 116 | text.replace(endRegex, '').trim() 117 | -------------------------------------------------------------------------------- /lib/todo-options-view.coffee: -------------------------------------------------------------------------------- 1 | {CompositeDisposable} = require 'atom' 2 | {View} = require 'atom-space-pen-views' 3 | 4 | class ItemView extends View 5 | @content: (item) -> 6 | @span class: 'badge badge-large', 'data-id': item, item 7 | 8 | class CodeView extends View 9 | @content: (item) -> 10 | @code item 11 | 12 | module.exports = 13 | class ShowTodoView extends View 14 | @content: -> 15 | @div outlet: 'todoOptions', class: 'todo-options', => 16 | @div class: 'option', => 17 | @h2 'On Table' 18 | @div outlet: 'itemsOnTable', class: 'block items-on-table' 19 | 20 | @div class: 'option', => 21 | @h2 'Off Table' 22 | @div outlet: 'itemsOffTable', class: 'block items-off-table' 23 | 24 | @div class: 'option', => 25 | @h2 'Find Todos' 26 | @div outlet: 'findTodoDiv' 27 | 28 | @div class: 'option', => 29 | @h2 'Find Regex' 30 | @div outlet: 'findRegexDiv' 31 | 32 | @div class: 'option', => 33 | @h2 'Ignore Paths' 34 | @div outlet: 'ignorePathDiv' 35 | 36 | @div class: 'option', => 37 | @h2 'Auto Refresh' 38 | @div class: 'checkbox', => 39 | @label => 40 | @input outlet: 'autoRefreshCheckbox', class: 'input-checkbox', type: 'checkbox' 41 | 42 | @div class: 'option', => 43 | @div class: 'btn-group', => 44 | @button outlet: 'configButton', class: 'btn', "Go to Config" 45 | @button outlet: 'closeButton', class: 'btn', "Close Options" 46 | 47 | initialize: (@collection) -> 48 | @disposables = new CompositeDisposable 49 | @handleEvents() 50 | @updateUI() 51 | 52 | handleEvents: -> 53 | @configButton.on 'click', -> 54 | atom.workspace.open 'atom://config/packages/todo-show' 55 | @closeButton.on 'click', => 56 | @parent().slideToggle() 57 | @autoRefreshCheckbox.on 'click', (event) => 58 | @autoRefreshChange(event.target.checked) 59 | 60 | @disposables.add atom.config.observe 'todo-show.autoRefresh', (newValue) => 61 | @autoRefreshCheckbox.context?.checked = newValue 62 | 63 | detach: -> 64 | @disposables.dispose() 65 | 66 | updateShowInTable: => 67 | showInTable = @sortable.toArray() 68 | atom.config.set('todo-show.showInTable', showInTable) 69 | 70 | updateUI: -> 71 | tableItems = atom.config.get('todo-show.showInTable') 72 | for item in @collection.getAvailableTableItems() 73 | if tableItems.indexOf(item) is -1 74 | @itemsOffTable.append new ItemView(item) 75 | else 76 | @itemsOnTable.append new ItemView(item) 77 | 78 | Sortable = require 'sortablejs' 79 | 80 | @sortable = Sortable.create( 81 | @itemsOnTable.context 82 | group: 'tableItems' 83 | ghostClass: 'ghost' 84 | onSort: @updateShowInTable 85 | ) 86 | 87 | Sortable.create( 88 | @itemsOffTable.context 89 | group: 'tableItems' 90 | ghostClass: 'ghost' 91 | ) 92 | 93 | for todo in todos = atom.config.get('todo-show.findTheseTodos') 94 | @findTodoDiv.append new CodeView(todo) 95 | 96 | regex = atom.config.get('todo-show.findUsingRegex') 97 | @findRegexDiv.append new CodeView(regex.replace('${TODOS}', todos.join('|'))) 98 | 99 | for path in atom.config.get('todo-show.ignoreThesePaths') 100 | @ignorePathDiv.append new CodeView(path) 101 | 102 | autoRefreshChange: (state) -> 103 | atom.config.set('todo-show.autoRefresh', state) 104 | -------------------------------------------------------------------------------- /lib/todo-regex.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | class TodoRegex 3 | constructor: (@regex, todoList) -> 4 | @error = false 5 | @regexp = @createRegexp(@regex, todoList) 6 | 7 | makeRegexObj: (regexStr = '') -> 8 | # Extract the regex pattern (anything between the slashes) 9 | pattern = regexStr.match(/\/(.+)\//)?[1] 10 | # Extract the flags (after last slash) 11 | flags = regexStr.match(/\/(\w+$)/)?[1] 12 | 13 | unless pattern 14 | @error = true 15 | return false 16 | new RegExp(pattern, flags) 17 | 18 | createRegexp: (regexStr, todoList) -> 19 | unless Object.prototype.toString.call(todoList) is '[object Array]' and 20 | todoList.length > 0 and 21 | regexStr 22 | @error = true 23 | return false 24 | @makeRegexObj(regexStr.replace('${TODOS}', todoList.join('|'))) 25 | -------------------------------------------------------------------------------- /lib/todo-show.js: -------------------------------------------------------------------------------- 1 | const {CompositeDisposable} = require('event-kit') 2 | 3 | const ShowTodoView = require('./todo-view') 4 | const TodoCollection = require('./todo-collection') 5 | const TodoIndicatorView = require('./todo-indicator-view') 6 | 7 | module.exports = 8 | class TodoShow { 9 | constructor() { 10 | this.URI = 'atom://todo-show' 11 | } 12 | 13 | activate() { 14 | this.createCollection() 15 | 16 | this.disposables = new CompositeDisposable 17 | this.disposables.add(atom.commands.add('atom-workspace', { 18 | 'todo-show:toggle': (event) => this.show(undefined, event), 19 | 'todo-show:find-in-workspace': () => this.show('workspace'), 20 | 'todo-show:find-in-project': () => this.show('project'), 21 | 'todo-show:find-in-open-files': () => this.show('open'), 22 | 'todo-show:find-in-active-file': () => this.show('active') 23 | })) 24 | 25 | this.disposables.add(atom.workspace.addOpener(uri => { 26 | if (uri === this.URI) { 27 | return this.deserializeTodoView() 28 | } 29 | })) 30 | } 31 | 32 | deactivate() { 33 | this.destroyTodoIndicator() 34 | if (this.showTodoView) this.showTodoView.destroy() 35 | if (this.disposables) this.disposables.dispose() 36 | this.showTodoView = null 37 | } 38 | 39 | deserializeTodoView(state = {}) { 40 | this.createCollection() 41 | 42 | if (state.scope) this.collection.setSearchScope(state.scope) 43 | if (state.customPath) this.collection.setCustomPath(state.customPath) 44 | 45 | if (this.showTodoView) { 46 | this.showTodoView.destroy() 47 | this.showTodoView = null 48 | } 49 | this.showTodoView = new ShowTodoView(this.collection, this.URI) 50 | 51 | // Make sure a search is executed when deserialized and visible 52 | if (state.deserializer) { 53 | setTimeout(() => { 54 | this.showTodoView.search(true) 55 | }, 1000) 56 | } 57 | 58 | return this.showTodoView 59 | } 60 | 61 | createCollection() { 62 | if (this.collection) return 63 | this.collection = new TodoCollection() 64 | 65 | const config = atom.config.getSchema('todo-show') 66 | if (config) { 67 | this.collection.setAvailableTableItems(config.properties.sortBy.enum) 68 | } 69 | } 70 | 71 | show(scope, event) { 72 | const path = this.getEventPath(event) 73 | if (path) { 74 | this.collection.setCustomPath(path) 75 | scope = 'custom' 76 | } 77 | 78 | if (scope) { 79 | const prevScope = this.collection.scope 80 | if (prevScope !== scope || path) { 81 | this.collection.setSearchScope(scope) 82 | if (this.showTodoView && this.showTodoView.isVisible()) return 83 | } 84 | } 85 | 86 | const prevPane = atom.workspace.getActivePane() 87 | atom.workspace.toggle(this.URI).then(item => { 88 | this.showTodoView = item 89 | if (item) { 90 | this.showTodoView.search(true) 91 | prevPane.activate() 92 | } 93 | }) 94 | } 95 | 96 | getEventPath(event) { 97 | if (event == null || event.target == null || event.target.getAttribute == null) { 98 | return 99 | } 100 | 101 | var path = event.target.getAttribute('data-path') 102 | if (path) { 103 | return atom.project.relativizePath(path)[1] 104 | } 105 | 106 | if (event.target.firstChild == null || event.target.firstChild.getAttribute == null) { 107 | return 108 | } 109 | 110 | path = event.target.firstChild.getAttribute('data-path') 111 | if (path) { 112 | return atom.project.relativizePath(path)[1] 113 | } 114 | } 115 | 116 | consumeStatusBar(statusBar) { 117 | atom.config.observe('todo-show.statusBarIndicator', newValue => { 118 | if (newValue) { 119 | if (!this.todoIndicatorView) { 120 | this.todoIndicatorView = new TodoIndicatorView(this.collection) 121 | this.statusBarTile = statusBar.addLeftTile({ 122 | item: this.todoIndicatorView, 123 | priority: 200 124 | }) 125 | } 126 | } else { 127 | this.destroyTodoIndicator() 128 | } 129 | }) 130 | } 131 | 132 | destroyTodoIndicator() { 133 | if (this.todoIndicatorView) this.todoIndicatorView.destroy() 134 | if (this.statusBarTile) this.statusBarTile.destroy() 135 | this.todoIndicatorView = null 136 | this.statusBarTile = null 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /lib/todo-table-view.coffee: -------------------------------------------------------------------------------- 1 | {CompositeDisposable} = require 'atom' 2 | {View, $} = require 'atom-space-pen-views' 3 | 4 | {TableHeaderView, TodoView, TodoEmptyView} = require './todo-item-view' 5 | 6 | module.exports = 7 | class ShowTodoView extends View 8 | @content: -> 9 | @div class: 'todo-table', tabindex: -1, => 10 | @table outlet: 'table' 11 | 12 | initialize: (@collection) -> 13 | @disposables = new CompositeDisposable 14 | @handleConfigChanges() 15 | @handleEvents() 16 | 17 | handleEvents: -> 18 | # @disposables.add @collection.onDidAddTodo @renderTodo 19 | @disposables.add @collection.onDidFinishSearch @initTable 20 | @disposables.add @collection.onDidRemoveTodo @removeTodo 21 | @disposables.add @collection.onDidClear @clearTodos 22 | @disposables.add @collection.onDidSortTodos (todos) => @renderTable todos 23 | @disposables.add @collection.onDidFilterTodos (todos) => @renderTable todos 24 | 25 | @on 'click', 'th', @tableHeaderClicked 26 | 27 | handleConfigChanges: -> 28 | @disposables.add atom.config.onDidChange 'todo-show.showInTable', ({newValue, oldValue}) => 29 | @showInTable = newValue 30 | @renderTable @collection.getTodos() 31 | 32 | @disposables.add atom.config.onDidChange 'todo-show.sortBy', ({newValue, oldValue}) => 33 | @sort(@sortBy = newValue, @sortAsc) 34 | 35 | @disposables.add atom.config.onDidChange 'todo-show.sortAscending', ({newValue, oldValue}) => 36 | @sort(@sortBy, @sortAsc = newValue) 37 | 38 | destroy: -> 39 | @disposables.dispose() 40 | @empty() 41 | 42 | initTable: => 43 | @showInTable = atom.config.get('todo-show.showInTable') 44 | @sortBy = atom.config.get('todo-show.sortBy') 45 | @sortAsc = atom.config.get('todo-show.sortAscending') 46 | @sort(@sortBy, @sortAsc) 47 | 48 | renderTableHeader: -> 49 | @table.append new TableHeaderView(@showInTable, {@sortBy, @sortAsc}) 50 | 51 | tableHeaderClicked: (e) => 52 | item = e.target.innerText 53 | sortAsc = if @sortBy is item then !@sortAsc else @sortAsc 54 | 55 | atom.config.set('todo-show.sortBy', item) 56 | atom.config.set('todo-show.sortAscending', sortAsc) 57 | 58 | renderTodo: (todo) => 59 | @table.append new TodoView(@showInTable, todo) 60 | 61 | removeTodo: (todo) -> 62 | console.log 'removeTodo' 63 | 64 | clearTodos: => 65 | @table.empty() 66 | 67 | renderTable: (todos) => 68 | @clearTodos() 69 | @renderTableHeader() 70 | 71 | for todo in todos = todos 72 | @renderTodo(todo) 73 | @table.append new TodoEmptyView(@showInTable) unless todos.length 74 | 75 | sort: (sortBy, sortAsc) -> 76 | @collection.sortTodos(sortBy: sortBy, sortAsc: sortAsc) 77 | -------------------------------------------------------------------------------- /lib/todo-view.coffee: -------------------------------------------------------------------------------- 1 | {CompositeDisposable, TextBuffer} = require 'atom' 2 | {ScrollView, TextEditorView} = require 'atom-space-pen-views' 3 | path = require 'path' 4 | fs = require 'fs-plus' 5 | 6 | TodoTable = require './todo-table-view' 7 | TodoOptions = require './todo-options-view' 8 | 9 | deprecatedTextEditor = (params) -> 10 | if atom.workspace.buildTextEditor? 11 | atom.workspace.buildTextEditor(params) 12 | else 13 | TextEditor = require('atom').TextEditor 14 | new TextEditor(params) 15 | 16 | module.exports = 17 | class ShowTodoView extends ScrollView 18 | @content: (collection, filterBuffer) -> 19 | # FIXME: Creating text editor this way results in weird getScopeChain error in Atom core - deprecated 20 | # filterEditor = deprecatedTextEditor( 21 | # mini: true 22 | # tabLength: 2 23 | # softTabs: true 24 | # softWrapped: false 25 | # buffer: filterBuffer 26 | # placeholderText: 'Search Todos' 27 | # ) 28 | 29 | @div class: 'show-todo-preview', tabindex: -1, => 30 | @div class: 'input-block', => 31 | @div class: 'input-block-item input-block-item--flex' 32 | # @subview 'filterEditorView', new TextEditorView(editor: filterEditor) 33 | @div class: 'input-block-item', => 34 | @div class: 'btn-group', => 35 | @button outlet: 'scopeButton', class: 'btn' 36 | @button outlet: 'optionsButton', class: 'btn icon-gear' 37 | @button outlet: 'exportButton', class: 'btn icon-cloud-download' 38 | @button outlet: 'refreshButton', class: 'btn icon-sync' 39 | 40 | @div class: 'input-block todo-info-block', => 41 | @div class: 'input-block-item', => 42 | @span outlet: 'todoInfo' 43 | 44 | @div outlet: 'optionsView' 45 | 46 | @div outlet: 'todoLoading', class: 'todo-loading', => 47 | @div class: 'markdown-spinner' 48 | @h5 outlet: 'searchCount', class: 'text-center', "Loading Todos..." 49 | 50 | @subview 'todoTable', new TodoTable(collection) 51 | 52 | constructor: (@collection, @uri) -> 53 | super @collection, @filterBuffer = new TextBuffer 54 | 55 | initialize: -> 56 | @disposables = new CompositeDisposable 57 | @handleEvents() 58 | @setScopeButtonState(@collection.getSearchScope()) 59 | 60 | @onlySearchWhenVisible = true 61 | @notificationOptions = 62 | detail: 'Atom todo-show package' 63 | dismissable: true 64 | icon: @getIconName() 65 | 66 | @checkDeprecation() 67 | 68 | @disposables.add atom.tooltips.add @scopeButton, title: "What to Search" 69 | @disposables.add atom.tooltips.add @optionsButton, title: "Show Todo Options" 70 | @disposables.add atom.tooltips.add @exportButton, title: "Export Todos" 71 | @disposables.add atom.tooltips.add @refreshButton, title: "Refresh Todos" 72 | 73 | handleEvents: -> 74 | @disposables.add atom.commands.add @element, 75 | 'core:export': (event) => 76 | event.stopPropagation() 77 | @export() 78 | 'core:refresh': (event) => 79 | event.stopPropagation() 80 | @search(true) 81 | 82 | @disposables.add @collection.onDidStartSearch @startLoading 83 | @disposables.add @collection.onDidFinishSearch @stopLoading 84 | @disposables.add @collection.onDidFailSearch (err) => 85 | @searchCount.text "Search Failed" 86 | console.error err if err 87 | @showError err if err 88 | 89 | @disposables.add @collection.onDidChangeSearchScope (scope) => 90 | @setScopeButtonState(scope) 91 | @search(true) 92 | 93 | @disposables.add @collection.onDidSearchPaths (nPaths) => 94 | @searchCount.text "#{nPaths} paths searched..." 95 | 96 | @disposables.add atom.workspace.onDidChangeActivePaneItem (item) => 97 | if @collection.setActiveProject(item?.getPath?()) or 98 | (item?.constructor.name is 'TextEditor' and @collection.scope is 'active') 99 | @search() 100 | 101 | @disposables.add atom.workspace.onDidAddTextEditor ({textEditor}) => 102 | @search() if @collection.scope is 'open' 103 | 104 | @disposables.add atom.workspace.onDidDestroyPaneItem ({item}) => 105 | @search() if @collection.scope is 'open' 106 | 107 | @disposables.add atom.workspace.observeTextEditors (editor) => 108 | @disposables.add editor.onDidSave => 109 | @search() 110 | 111 | # @filterEditorView.getModel().onDidStopChanging => 112 | # @filter() if @firstTimeFilter 113 | # @firstTimeFilter = true 114 | 115 | @scopeButton.on 'click', @toggleSearchScope 116 | @optionsButton.on 'click', @toggleOptions 117 | @exportButton.on 'click', @export 118 | @refreshButton.on 'click', => @search(true) 119 | 120 | destroy: -> 121 | @collection.cancelSearch() 122 | @disposables.dispose() 123 | @detach() 124 | 125 | serialize: -> 126 | deserializer: 'todo-show/todo-view' 127 | scope: @collection.scope 128 | customPath: @collection.getCustomPath() 129 | 130 | getTitle: -> "Todo Show" 131 | getIconName: -> "checklist" 132 | getURI: -> @uri 133 | getDefaultLocation: -> 'right' 134 | getAllowedLocations: -> ['left', 'right', 'bottom'] 135 | getProjectName: -> @collection.getActiveProjectName() 136 | getProjectPath: -> @collection.getActiveProject() 137 | 138 | getTodos: -> @collection.getTodos() 139 | getTodosCount: -> @collection.getTodosCount() 140 | isSearching: -> @collection.getState() 141 | search: (force = false) -> 142 | if @onlySearchWhenVisible 143 | return unless atom.workspace.paneContainerForItem(this)?.isVisible() 144 | @collection.search(force) 145 | 146 | startLoading: => 147 | @todoLoading.show() 148 | @updateInfo() 149 | 150 | stopLoading: => 151 | @todoLoading.hide() 152 | @updateInfo() 153 | 154 | updateInfo: -> 155 | @todoInfo.html("#{@getInfoText()} #{@getScopeText()}") 156 | 157 | getInfoText: -> 158 | return "Found ... results" if @isSearching() 159 | switch count = @getTodosCount() 160 | when 1 then "Found #{count} result" 161 | else "Found #{count} results" 162 | 163 | getScopeText: -> 164 | # TODO: Also show number of files 165 | 166 | switch @collection.scope 167 | when 'active' 168 | "in active file" 169 | when 'open' 170 | "in open files" 171 | when 'project' 172 | "in project #{@getProjectName()}" 173 | when 'custom' 174 | "in #{@collection.customPath}" 175 | else 176 | "in workspace" 177 | 178 | showError: (message = '') -> 179 | atom.notifications.addError message.toString(), @notificationOptions 180 | 181 | showWarning: (message = '') -> 182 | atom.notifications.addWarning message.toString(), @notificationOptions 183 | 184 | export: => 185 | return if @isSearching() 186 | 187 | filePath = "#{@getProjectName() or 'todos'}.md" 188 | if projectPath = @getProjectPath() 189 | filePath = path.join(projectPath, filePath) 190 | 191 | # Do not override if default file path already exists 192 | filePath = undefined if fs.existsSync(filePath) 193 | 194 | atom.workspace.open(filePath).then (textEditor) => 195 | textEditor.setText(@collection.getMarkdown()) 196 | 197 | toggleSearchScope: => 198 | scope = @collection.toggleSearchScope() 199 | @setScopeButtonState(scope) 200 | 201 | setScopeButtonState: (state) => 202 | switch state 203 | when 'project' then @scopeButton.text 'Project' 204 | when 'open' then @scopeButton.text 'Open Files' 205 | when 'active' then @scopeButton.text 'Active File' 206 | when 'custom' then @scopeButton.text 'Custom' 207 | else @scopeButton.text 'Workspace' 208 | 209 | toggleOptions: => 210 | unless @todoOptions 211 | @optionsView.hide() 212 | @todoOptions = new TodoOptions(@collection) 213 | @optionsView.html @todoOptions 214 | @optionsView.slideToggle() 215 | 216 | filter: -> 217 | @collection.filterTodos @filterBuffer.getText() 218 | 219 | checkDeprecation: -> 220 | if atom.config.get('todo-show.findTheseRegexes') 221 | @showWarning ''' 222 | Deprecation Warning:\n 223 | `findTheseRegexes` config is deprecated, please use `findTheseTodos` and `findUsingRegex` for custom behaviour. 224 | See https://github.com/mrodalgaard/atom-todo-show#config for more information. 225 | ''' 226 | -------------------------------------------------------------------------------- /menus/show-todo.cson: -------------------------------------------------------------------------------- 1 | 'menu': [ 2 | 'label': 'Packages' 3 | 'submenu': [ 4 | 'label': 'Todo-Show' 5 | 'submenu': [ 6 | { 7 | 'label': 'Find in Workspace' 8 | 'command': 'todo-show:find-in-workspace' 9 | } 10 | { 11 | 'label': 'Find in Project' 12 | 'command': 'todo-show:find-in-project' 13 | } 14 | { 15 | 'label': 'Find in Open Files' 16 | 'command': 'todo-show:find-in-open-files' 17 | } 18 | { 19 | 'label': 'Find in Active File' 20 | 'command': 'todo-show:find-in-active-file' 21 | } 22 | ] 23 | ] 24 | ] 25 | 26 | 'context-menu': 27 | '.show-todo-preview': [ 28 | {label: 'Export Todos', command: 'core:export'} 29 | {label: 'Refresh Todos', command: 'core:refresh'} 30 | ], 31 | '.tree-view': [ 32 | {label: 'Search for Todos', command: 'todo-show:toggle'} 33 | ] 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo-show", 3 | "version": "2.3.2", 4 | "main": "./lib/main", 5 | "description": "Finds all the TODOs, FIXMEs, CHANGEDs, etc. in your project.", 6 | "repository": "https://github.com/mrodalgaard/atom-todo-show", 7 | "license": "MIT", 8 | "engines": { 9 | "atom": ">1.17.0" 10 | }, 11 | "dependencies": { 12 | "atom-space-pen-views": "^2.0.3", 13 | "event-kit": "^2.3.0", 14 | "fs-plus": "2.x", 15 | "sortablejs": "^1.4.2", 16 | "temp": "^0.8.3", 17 | "underscore-plus": "^1.6.6" 18 | }, 19 | "deserializers": { 20 | "todo-show/todo-view": "deserializeTodoView" 21 | }, 22 | "consumedServices": { 23 | "status-bar": { 24 | "versions": { 25 | "^1.0.0": "consumeStatusBar" 26 | } 27 | } 28 | }, 29 | "configSchema": { 30 | "autoRefresh": { 31 | "type": "boolean", 32 | "default": true 33 | }, 34 | "findTheseTodos": { 35 | "description": "An array of todo types used by the search regex.", 36 | "type": "array", 37 | "default": [ 38 | "TODO", 39 | "FIXME", 40 | "CHANGED", 41 | "XXX", 42 | "IDEA", 43 | "HACK", 44 | "NOTE", 45 | "REVIEW", 46 | "NB", 47 | "BUG", 48 | "QUESTION", 49 | "COMBAK", 50 | "TEMP" 51 | ], 52 | "items": { 53 | "type": "string" 54 | } 55 | }, 56 | "findUsingRegex": { 57 | "description": "Regex string used to find all your todos. `${TODOS}` is replaced with `FindTheseTodos` from above.", 58 | "type": "string", 59 | "default": "/\\b(${TODOS})[:;.,]?\\d*($|\\s.*$|[\\{\\[\\(].+$)/g" 60 | }, 61 | "ignoreThesePaths": { 62 | "description": "Similar to `.gitignore` (remember to use `/` on Mac/Linux and `\\` on Windows for subdirectories).", 63 | "type": "array", 64 | "default": [ 65 | "node_modules", 66 | "vendor", 67 | "bower_components", 68 | "*.pdf" 69 | ], 70 | "items": { 71 | "type": "string" 72 | } 73 | }, 74 | "showInTable": { 75 | "description": "An array of properties to show for each todo in table.", 76 | "type": "array", 77 | "default": [ 78 | "Text", 79 | "Type", 80 | "Path" 81 | ] 82 | }, 83 | "sortBy": { 84 | "type": "string", 85 | "default": "Text", 86 | "enum": [ 87 | "All", 88 | "Text", 89 | "Type", 90 | "Range", 91 | "Line", 92 | "Regex", 93 | "Path", 94 | "File", 95 | "Tags", 96 | "Id", 97 | "Project" 98 | ] 99 | }, 100 | "sortAscending": { 101 | "type": "boolean", 102 | "default": true 103 | }, 104 | "exportAs": { 105 | "type": "string", 106 | "default": "List", 107 | "enum": [ 108 | "List", 109 | "Table" 110 | ] 111 | }, 112 | "statusBarIndicator": { 113 | "type": "boolean", 114 | "default": false 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /styles/show-todo.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | .show-todo-preview { 4 | @todo-color: @base-background-color; 5 | @todo-color-light: lighten(@todo-color, 5%); 6 | 7 | .todo-font { 8 | font-size: 12px; 9 | font-weight: bold; 10 | } 11 | 12 | .todo-loading { 13 | padding: @component-padding; 14 | } 15 | 16 | background-color: @todo-color; 17 | color: @text-color; 18 | overflow: scroll; 19 | 20 | .input-block { 21 | @item-width: 200px; 22 | 23 | display: flex; 24 | flex-wrap: wrap; 25 | width: 100%; 26 | 27 | .input-block-item { 28 | display: flex; 29 | flex: 1 1 @item-width; 30 | padding: @component-padding / 2 @component-padding; 31 | 32 | .btn-group { 33 | display: flex; 34 | flex: 1 1 220px; 35 | min-height: 28px; 36 | 37 | .btn { 38 | flex: 1 1 @item-width; 39 | line-height: 12px; 40 | text-align: center; 41 | } 42 | .btn:first-child { 43 | flex: 1 1 @item-width * 2.5; 44 | } 45 | } 46 | } 47 | 48 | .input-block-item--flex { 49 | flex: 100 1 @item-width; 50 | position: relative; 51 | atom-text-editor { 52 | width: 100%; 53 | } 54 | } 55 | 56 | &.todo-info-block { 57 | padding-left: 5px; 58 | } 59 | } 60 | 61 | .todo-options { 62 | border: 2px solid @todo-color-light; 63 | margin: @component-padding / 2 @component-padding; 64 | padding: 0 @component-padding / 2 @component-padding / 2; 65 | 66 | .option { 67 | display: flex; 68 | align-items: center; 69 | & > h2 { 70 | .todo-font; 71 | flex: 1 0 80px; 72 | } 73 | & > div { 74 | word-wrap: break-word; 75 | word-break: break-word; 76 | overflow: hidden; 77 | line-height: 18px; 78 | flex: 100; 79 | } 80 | .checkbox { 81 | margin-bottom: @component-padding / 2; 82 | } 83 | 84 | .items-on-table, .items-off-table { 85 | min-height: 20px; 86 | } 87 | } 88 | 89 | .items-on-table, .items-off-table { 90 | .badge { 91 | .todo-font; 92 | width: 56px; 93 | margin: @component-padding / 4; 94 | cursor: move; 95 | } 96 | 97 | .ghost { 98 | opacity: 0.3; 99 | } 100 | } 101 | } 102 | 103 | .todo-table { 104 | margin: @component-padding / 2 @component-padding; 105 | 106 | table { 107 | width: 100%; 108 | } 109 | 110 | tr { 111 | &:nth-child(2n + 1) { 112 | background-color: @todo-color-light; 113 | } 114 | &:hover { 115 | background-color: lighten(@todo-color-light, 5%); 116 | } 117 | } 118 | 119 | td + td, th + th { 120 | border-left: 1px groove @text-color; 121 | } 122 | 123 | th, td { 124 | padding: 6px; 125 | word-break: break-word; 126 | cursor: pointer; 127 | } 128 | 129 | th { 130 | resize: both; 131 | overflow: auto; 132 | 133 | .todo-font; 134 | min-width: 75px; 135 | 136 | .sort-asc, .sort-desc { 137 | float: right; 138 | pointer-events: none; 139 | &:not(.active) { 140 | opacity: 0.2; 141 | } 142 | } 143 | .sort-asc { 144 | margin: 5px 0 -5px 0; 145 | } 146 | .sort-desc { 147 | margin: -4px -16px 0 0; 148 | } 149 | } 150 | } 151 | 152 | code { 153 | margin-right: 2px; 154 | color: @text-color; 155 | } 156 | } 157 | 158 | .theme-atom-material-ui .todo-table tr { 159 | &:nth-child(2n + 1), &:hover { 160 | background-color: @base-border-color; 161 | } 162 | } 163 | --------------------------------------------------------------------------------