├── 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 [](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 | 
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 |
--------------------------------------------------------------------------------