diff --git a/.gitignore b/.gitignore
index 31bfaa5..2be9c7c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,2 @@
-node_modules
-gulpfile.js
-package.json
*.sublime-project
*.sublime-workspace
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 96ef3b3..d3ee86e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,7 @@
- Doc: New features
- Doc: Outlined arguments (#63)
- Add: `patterns_weight` option (#58)
-- Add: `render_max_spaces` option (#59)
+- Add: `render_maxspaces` option (#59)
- Del: `render_spaces` in favor of maxspaces (#59)
- Add: `render_header_date` option
- Add: `render_header_format` option
diff --git a/Default (Linux).sublime-mousemap b/Default (Linux).sublime-mousemap
deleted file mode 100644
index b21f59e..0000000
--- a/Default (Linux).sublime-mousemap
+++ /dev/null
@@ -1,8 +0,0 @@
-[
- {
- "button": "button1",
- "count": 2,
- "modifiers": ["alt"],
- "command": "mouse_goto_comment"
- }
-]
\ No newline at end of file
diff --git a/Default (OSX).sublime-keymap b/Default (OSX).sublime-keymap
deleted file mode 100644
index 654c7a0..0000000
--- a/Default (OSX).sublime-keymap
+++ /dev/null
@@ -1,73 +0,0 @@
-[
-
- {
- "keys": ["down"], "command": "navigate_results",
- "context": [
- {"key": "setting.command_mode", "operand": true},
- {"key": "setting.todo_results"}
- ],
- "args": {"direction": "forward"}
- },
-
- {
- "keys": ["j"], "command": "navigate_results",
- "context": [
- {"key": "setting.command_mode", "operand": true},
- {"key": "setting.todo_results"}
- ],
- "args": {"direction": "forward"}
- },
-
- {
- "keys": ["pagedown"], "command": "navigate_results",
- "context": [
- {"key": "setting.command_mode", "operand": true},
- {"key": "setting.todo_results"}
- ],
- "args": {"direction": "forward_skip"}
- },
-
- {
- "keys": ["up"], "command": "navigate_results",
- "context": [
- {"key": "setting.command_mode", "operand": true},
- {"key": "setting.todo_results"}
- ],
- "args": {"direction": "backward"}
- },
-
- {
- "keys": ["k"], "command": "navigate_results",
- "context": [
- {"key": "setting.command_mode", "operand": true},
- {"key": "setting.todo_results"}
- ],
- "args": {"direction": "backward"}
- },
-
- {
- "keys": ["pageup"], "command": "navigate_results",
- "context": [
- {"key": "setting.command_mode", "operand": true},
- {"key": "setting.todo_results"}
- ],
- "args": {"direction": "backward_skip"}
- },
-
- {
- "keys": ["c"], "command": "clear_selection",
- "context": [
- {"key": "setting.command_mode", "operand": true},
- {"key": "setting.todo_results"}
- ]
- },
-
- {
- "keys": ["enter"], "command": "goto_comment",
- "context": [
- {"key": "setting.command_mode", "operand": true},
- {"key": "setting.todo_results"}
- ]
- }
-
-]
diff --git a/Default (OSX).sublime-mousemap b/Default (OSX).sublime-mousemap
deleted file mode 100644
index b21f59e..0000000
--- a/Default (OSX).sublime-mousemap
+++ /dev/null
@@ -1,8 +0,0 @@
-[
- {
- "button": "button1",
- "count": 2,
- "modifiers": ["alt"],
- "command": "mouse_goto_comment"
- }
-]
\ No newline at end of file
diff --git a/Default (Windows).sublime-keymap b/Default (Windows).sublime-keymap
deleted file mode 100644
index 654c7a0..0000000
--- a/Default (Windows).sublime-keymap
+++ /dev/null
@@ -1,73 +0,0 @@
-[
-
- {
- "keys": ["down"], "command": "navigate_results",
- "context": [
- {"key": "setting.command_mode", "operand": true},
- {"key": "setting.todo_results"}
- ],
- "args": {"direction": "forward"}
- },
-
- {
- "keys": ["j"], "command": "navigate_results",
- "context": [
- {"key": "setting.command_mode", "operand": true},
- {"key": "setting.todo_results"}
- ],
- "args": {"direction": "forward"}
- },
-
- {
- "keys": ["pagedown"], "command": "navigate_results",
- "context": [
- {"key": "setting.command_mode", "operand": true},
- {"key": "setting.todo_results"}
- ],
- "args": {"direction": "forward_skip"}
- },
-
- {
- "keys": ["up"], "command": "navigate_results",
- "context": [
- {"key": "setting.command_mode", "operand": true},
- {"key": "setting.todo_results"}
- ],
- "args": {"direction": "backward"}
- },
-
- {
- "keys": ["k"], "command": "navigate_results",
- "context": [
- {"key": "setting.command_mode", "operand": true},
- {"key": "setting.todo_results"}
- ],
- "args": {"direction": "backward"}
- },
-
- {
- "keys": ["pageup"], "command": "navigate_results",
- "context": [
- {"key": "setting.command_mode", "operand": true},
- {"key": "setting.todo_results"}
- ],
- "args": {"direction": "backward_skip"}
- },
-
- {
- "keys": ["c"], "command": "clear_selection",
- "context": [
- {"key": "setting.command_mode", "operand": true},
- {"key": "setting.todo_results"}
- ]
- },
-
- {
- "keys": ["enter"], "command": "goto_comment",
- "context": [
- {"key": "setting.command_mode", "operand": true},
- {"key": "setting.todo_results"}
- ]
- }
-
-]
diff --git a/Default (Windows).sublime-mousemap b/Default (Windows).sublime-mousemap
deleted file mode 100644
index b21f59e..0000000
--- a/Default (Windows).sublime-mousemap
+++ /dev/null
@@ -1,8 +0,0 @@
-[
- {
- "button": "button1",
- "count": 2,
- "modifiers": ["alt"],
- "command": "mouse_goto_comment"
- }
-]
\ No newline at end of file
diff --git a/Default.sublime-commands b/Default.sublime-commands
index 9fcc49d..1e21c7c 100644
--- a/Default.sublime-commands
+++ b/Default.sublime-commands
@@ -1,11 +1,11 @@
[
- {
- "caption": "TodoReview: Project Files",
- "command": "todo_review"
- },
- {
- "caption": "TodoReview: Project and Open Files",
- "command": "todo_review",
- "args": { "open_files": true }
- }
+ {
+ "caption": "TodoReview: Project Files",
+ "command": "todo_review"
+ },
+ {
+ "caption": "TodoReview: Project and Open Files",
+ "command": "todo_review",
+ "args": { "open_files": true }
+ }
]
\ No newline at end of file
diff --git a/Default (Linux).sublime-keymap b/Default.sublime-keymap
similarity index 57%
rename from Default (Linux).sublime-keymap
rename to Default.sublime-keymap
index 654c7a0..b9f31f7 100644
--- a/Default (Linux).sublime-keymap
+++ b/Default.sublime-keymap
@@ -1,73 +1,75 @@
[
{
- "keys": ["down"], "command": "navigate_results",
+ "keys": ["down"], "command": "todo_review_results",
"context": [
{"key": "setting.command_mode", "operand": true},
{"key": "setting.todo_results"}
],
- "args": {"direction": "forward"}
+ "args": {"direction": "down"}
},
{
- "keys": ["j"], "command": "navigate_results",
+ "keys": ["j"], "command": "todo_review_results",
"context": [
{"key": "setting.command_mode", "operand": true},
{"key": "setting.todo_results"}
],
- "args": {"direction": "forward"}
+ "args": {"direction": "down"}
},
{
- "keys": ["pagedown"], "command": "navigate_results",
+ "keys": ["pagedown"], "command": "todo_review_results",
"context": [
{"key": "setting.command_mode", "operand": true},
{"key": "setting.todo_results"}
],
- "args": {"direction": "forward_skip"}
+ "args": {"direction": "down_skip"}
},
{
- "keys": ["up"], "command": "navigate_results",
+ "keys": ["up"], "command": "todo_review_results",
"context": [
{"key": "setting.command_mode", "operand": true},
{"key": "setting.todo_results"}
],
- "args": {"direction": "backward"}
+ "args": {"direction": "up"}
},
{
- "keys": ["k"], "command": "navigate_results",
+ "keys": ["k"], "command": "todo_review_results",
"context": [
{"key": "setting.command_mode", "operand": true},
{"key": "setting.todo_results"}
],
- "args": {"direction": "backward"}
+ "args": {"direction": "up"}
},
{
- "keys": ["pageup"], "command": "navigate_results",
+ "keys": ["pageup"], "command": "todo_review_results",
"context": [
{"key": "setting.command_mode", "operand": true},
{"key": "setting.todo_results"}
],
- "args": {"direction": "backward_skip"}
+ "args": {"direction": "up_skip"}
},
{
- "keys": ["c"], "command": "clear_selection",
+ "keys": ["enter"], "command": "todo_review_results",
"context": [
{"key": "setting.command_mode", "operand": true},
{"key": "setting.todo_results"}
- ]
+ ],
+ "args": {"open": true}
},
{
- "keys": ["enter"], "command": "goto_comment",
+ "keys": ["r"], "command": "todo_review_results",
"context": [
{"key": "setting.command_mode", "operand": true},
{"key": "setting.todo_results"}
- ]
+ ],
+ "args": {"refresh": true}
}
]
diff --git a/Main.sublime-menu b/Main.sublime-menu
index f076f2a..99e57cc 100644
--- a/Main.sublime-menu
+++ b/Main.sublime-menu
@@ -26,50 +26,16 @@
{
"command": "open_file",
"args": {
- "file": "${packages}/TodoReview/Default (OSX).sublime-keymap",
- "platform": "OSX"
+ "file": "${packages}/TodoReview/Default.sublime-keymap"
},
"caption": "Key Bindings – Default"
},
{
"command": "open_file",
"args": {
- "file": "${packages}/TodoReview/Default (Linux).sublime-keymap",
- "platform": "Linux"
+ "file": "${packages}/TodoReview/Default.sublime-mousemap"
},
"caption": "Key Bindings – Default"
- },
- {
- "command": "open_file",
- "args": {
- "file": "${packages}/TodoReview/Default (Windows).sublime-keymap",
- "platform": "Windows"
- },
- "caption": "Key Bindings – Default"
- },
- {
- "command": "open_file",
- "args": {
- "file": "${packages}/User/Default (OSX).sublime-keymap",
- "platform": "OSX"
- },
- "caption": "Key Bindings – User"
- },
- {
- "command": "open_file",
- "args": {
- "file": "${packages}/User/Default (Linux).sublime-keymap",
- "platform": "Linux"
- },
- "caption": "Key Bindings – User"
- },
- {
- "command": "open_file",
- "args": {
- "file": "${packages}/User/Default (Windows).sublime-keymap",
- "platform": "Windows"
- },
- "caption": "Key Bindings – User"
}
]
}
diff --git a/TodoReview.hidden-tmLanguage b/TodoReview.hidden-tmLanguage
index bc9b131..8bf867d 100644
--- a/TodoReview.hidden-tmLanguage
+++ b/TodoReview.hidden-tmLanguage
@@ -2,66 +2,76 @@
- name
- TodoReview
+ name
+ TodoReview
- patterns
-
-
- comment
- Titles
+ patterns
+
+
+ comment
+ Header
- match
- ^.*##.*$
+ match
+ ^\/\/.*$
- name
- string
-
-
- comment
- Line Num
+ name
+ comment
+
+
+ comment
+ Titles
- match
- :[0-9]+\s
+ match
+ ^##.*$
- name
- entity.name.function
-
-
- comment
- Priority
+ name
+ string
+
+
+ comment
+ Line Num
- match
- \(([0-9]{1,2})\)
+ match
+ :[0-9]+\s
- name
- variable
-
-
- comment
- Brackets
+ name
+ entity.name.function
+
+
+ comment
+ Priority
- match
- \[(.*?)\]
+ match
+ \(([0-9]{1,2})\)
- name
- entity.name.class
-
-
- comment
- @tags
+ name
+ variable
+
+
+ comment
+ Brackets
- match
- \@\S+
+ match
+ \[(.*?)\]
- name
- keyword
-
-
+ name
+ entity.name.class
+
+
+ comment
+ @tags
- scopeName
- text.todo-list
- uuid
- c67743c1-8072-45e2-8229-271c169b60d0
+ match
+ \@\S+
+
+ name
+ keyword
+
+
+
+ scopeName
+ text.todo-list
+ uuid
+ c67743c1-8072-45e2-8229-271c169b60d0
diff --git a/TodoReview.py b/TodoReview.py
index edde071..0bb94dc 100644
--- a/TodoReview.py
+++ b/TodoReview.py
@@ -1,349 +1,398 @@
'''
SublimeTodoReview
-A SublimeText 3 plugin for reviewing todo (any other) comments within your code.
+A SublimeText 3 plugin for reviewing todo (and other) comments within your code.
@author Jonathan Delgado (Initial Repo by @robcowie and ST3 update by @dnatag)
'''
-from collections import namedtuple
-from datetime import datetime
-from itertools import groupby
-from os import path, walk
-import sublime_plugin
-import threading
-import sublime
-import functools
+import datetime
import fnmatch
+import itertools
+import os
import re
-
-Message = namedtuple('Message', 'type, msg')
-
-
-def do_when(conditional, callback, *args, **kwargs):
- if conditional():
- return callback(*args, **kwargs)
- sublime.set_timeout(functools.partial(do_when, conditional, callback, *args, **kwargs), 50)
+import sublime
+import sublime
+import sublime_plugin
+import threading
+import timeit
class Settings():
+
def __init__(self, view):
self.user = sublime.load_settings('TodoReview.sublime-settings')
- self.view = view.settings().get('todoreview', {})
-
- def get(self, item, default):
- return self.view.get(item, self.user.get(item, default))
-
-class ThreadProgress(object):
- def __init__(self, thread, message, success_message, file_counter):
- self.thread = thread
- self.message = message
- self.success_message = success_message
- self.file_counter = file_counter
- self.addend = 1
- self.size = 8
- sublime.set_timeout(lambda: self.run(0), 100)
-
- def run(self, i):
- if not self.thread.is_alive():
- if hasattr(self.thread, 'result') and not self.thread.result:
- sublime.status_message('')
- return
- sublime.status_message(self.success_message)
- return
- before = i % self.size
- after = (self.size - 1) - before
- sublime.status_message('%s [%s=%s] (%s files scanned)' % (self.message, ' ' * before, ' ' * after, self.file_counter))
- if not after:
- self.addend = -1
- if not before:
- self.addend = 1
- i += self.addend
- sublime.set_timeout(lambda: self.run(i), 100)
-
-class TodoExtractor(object):
- def __init__(self, dirpaths, filepaths, file_counter):
+ self.proj = view.settings().get('todoreview', {})
+
+
+ def get(self, key, default):
+ return self.proj.get(key, self.user.get(key, default))
+
+
+class Engine():
+
+ def __init__(self, dirpaths, filepaths, view):
+ self.view = view
self.dirpaths = dirpaths
self.filepaths = filepaths
- self.patterns = settings.get('patterns', {})
- self.file_counter = file_counter
- self.ignored_files = [fnmatch.translate(patt) for patt in settings.get('exclude_files', [])]
- self.ignored_folders = [fnmatch.translate(patt) for patt in settings.get('exclude_folders', [])]
- def iter_files(self):
- seen_paths_ = []
- exclude_folders = [re.compile(patt) for patt in self.ignored_folders]
+ if settings.get('case_sensitive', False):
+ case = 0
+ else:
+ case = re.IGNORECASE
- for filepath in self.filepaths:
- pth = path.realpath(path.expanduser(path.abspath(filepath)))
- if pth not in seen_paths_:
- seen_paths_.append(pth)
- yield pth
+ patt_patterns = settings.get('patterns', {})
+ patt_files = settings.get('exclude_files', [])
+ patt_folders = settings.get('exclude_folders', [])
+
+ match_patterns = '|'.join(patt_patterns.values())
+ match_files = [fnmatch.translate(p) for p in patt_files]
+ match_folders = [fnmatch.translate(p) for p in patt_folders]
+
+ self.patterns = re.compile(match_patterns, case)
+ self.priority = re.compile(r'\(([0-9]{1,2})\)')
+ self.exclude_files = [re.compile(p) for p in match_files]
+ self.exclude_folders = [re.compile(p) for p in match_folders]
+
+ self.open = self.view.window().views()
+ self.open_files = [v.file_name() for v in self.open if v.file_name()]
+
+
+ def files(self):
+ seen_paths = []
for dirpath in self.dirpaths:
- dirpath = path.abspath(dirpath)
- for dirpath, dirnames, filenames in walk(dirpath):
+ for dirp, dirnames, filepaths in os.walk(self.resolve(dirpath)):
- if any(patt.search(dirpath) for patt in exclude_folders):
+ if any(p.search(dirp) for p in self.exclude_folders):
continue
- for filepath in filenames:
- pth = path.join(dirpath, filepath)
- pth = path.realpath(path.expanduser(path.abspath(pth)))
- if pth not in seen_paths_:
- seen_paths_.append(pth)
- yield pth
+ for filepath in filepaths:
+ self.filepaths.append(os.path.join(dirp, filepath))
- def filter_files(self, files):
- exclude_files = [re.compile(patt) for patt in self.ignored_files]
- for filepath in files:
- if any(patt.search(filepath) for patt in exclude_files):
+ for filepath in self.filepaths:
+ p = self.resolve(filepath)
+ if p in seen_paths:
continue
- yield filepath
- def search_targets(self):
- return self.filter_files(self.iter_files())
+ if any(p.search(filepath) for p in self.exclude_folders):
+ continue
- def extract(self):
- message_patterns = '|'.join(self.patterns.values())
- case_sensitivity = 0 if settings.get('case_sensitive', False) else re.IGNORECASE
- patt = re.compile(message_patterns, case_sensitivity)
- patt_priority = re.compile(r'\(([0-9]{1,2})\)')
- for filepath in self.search_targets():
- try:
- f = open(filepath, 'r', encoding='utf-8')
- for linenum, line in enumerate(f):
- for mo in patt.finditer(line):
+ if any(p.search(filepath) for p in self.exclude_files):
+ continue
- matches = [Message(msg_type, msg) for msg_type, msg in mo.groupdict().items() if msg]
- for matchi in matches:
- priority = patt_priority.search(matchi.msg)
+ seen_paths.append(p)
+ yield p
- if priority:
- priority = int(priority.group(0).replace('(', '').replace(')', ''))
+
+ def extract(self, files):
+ for p in files:
+ try:
+ if p in self.open_files:
+ for view in self.open:
+ if view.file_name() == p:
+ f = []
+ lines = view.lines(sublime.Region(0, view.size()))
+ for line in lines:
+ f.append(view.substr(line))
+ break
+ else:
+ f = open(p, 'r')
+
+ for num, line in enumerate(f, 1):
+ for result in self.patterns.finditer(line):
+ for patt, note in result.groupdict().items():
+
+ if not note:
+ continue
+
+ priority_match = self.priority.search(note)
+
+ if(priority_match):
+ priority = int(priority_match.group(1))
else:
priority = 100
yield {
- 'filepath': filepath,
- 'linenum': linenum + 1,
- 'match': matchi,
+ 'file': p,
+ 'patt': patt,
+ 'note': note,
+ 'line': num,
'priority': priority
}
- except (IOError, UnicodeDecodeError):
+
+ except(IOError, UnicodeDecodeError):
f = None
+
finally:
- self.file_counter.increment()
- if f is not None:
+ thread.increment()
+ if f is not None and type(f) is not list:
f.close()
-class RenderResultRunCommand(sublime_plugin.TextCommand):
- def run(self, edit, formatted_results, file_counter):
- active_window = sublime.active_window()
- existing_results = [v for v in active_window.views() if v.name() == 'TodoReview' and v.is_scratch()]
- if existing_results:
- result_view = existing_results[0]
- else:
- result_view = active_window.new_file()
- result_view.set_name('TodoReview')
- result_view.set_scratch(True)
- result_view.settings().set('todo_results', True)
- hr = u'+ {0} +'.format('-' * 56)
- header = u'{hr}\n| TodoReview @ {0:<43} |\n| {1:<56} |\n{hr}\n'.format(datetime.now().strftime('%A %m/%d/%y at %I:%M%p'), u'{0} files scanned'.format(file_counter), hr=hr)
+ def process(self):
+ return self.extract(self.files())
- result_view.erase(edit, sublime.Region(0, result_view.size()))
- result_view.insert(edit, result_view.size(), header)
- regions_data = [x[:] for x in [[]] * 2]
+ def resolve(self, directory):
+ return os.path.realpath(os.path.expanduser(os.path.abspath(directory)))
- for linetype, line, data in formatted_results:
- insert_point = result_view.size()
- result_view.insert(edit, insert_point, line)
- if linetype == 'result':
- rgn = sublime.Region(insert_point, result_view.size())
- regions_data[0].append(rgn)
- regions_data[1].append(data)
- result_view.insert(edit, result_view.size(), u'\n')
+class Thread(threading.Thread):
- result_view.add_regions('results', regions_data[0], '')
-
- d_ = dict(('{0},{1}'.format(k.a, k.b), v) for k, v in zip(regions_data[0], regions_data[1]))
- result_view.settings().set('result_regions', d_)
-
- result_view.assign_syntax('Packages/TodoReview/TodoReview.hidden-tmLanguage')
- result_view.settings().set('line_padding_bottom', 2)
- result_view.settings().set('line_padding_top', 2)
- result_view.settings().set('word_wrap', False)
- result_view.settings().set('command_mode', True)
- active_window.focus_view(result_view)
-
-class WorkerThread(threading.Thread):
-
- def __init__(self, extractor, callback, file_counter):
- self.extractor = extractor
+ def __init__(self, engine, callback):
+ self.i = 0;
+ self.engine = engine
self.callback = callback
- self.file_counter = file_counter
+ self.lock = threading.RLock()
threading.Thread.__init__(self)
- def run(self):
-
- todos = self.extractor.extract()
- formatted = list(self.format(todos))
- self.callback(formatted, self.file_counter)
-
- def format(self, messages):
- messages = sorted(messages, key=lambda m: (m['priority'], m['match'].type))
-
- for message_type, matches in groupby(messages, key=lambda m: m['match'].type):
- matches = list(matches)
- if matches:
- yield ('header', u'\n## {0} ({1})'.format(message_type.upper(), len(matches)), {})
- for idx, m in enumerate(matches, 1):
- msg = m['match'].msg
-
- if settings.get('render_include_folder', False):
- filepath = path.dirname(m['filepath']).replace('\\', '/').split('/')
- filepath = filepath[len(filepath) - 1] + '/' + path.basename(m['filepath'])
- else:
- filepath = path.basename(m['filepath'])
- spaces = ' '*(settings.get('render_spaces', 1) - len(str(idx) + filepath + ':' + str(m['linenum'])))
- line = u'{idx}. {filepath}:{linenum}{spaces}{msg}'.format(idx=idx, filepath=filepath, linenum=m['linenum'], spaces=spaces, msg=msg)
- yield ('result', line, m)
+ def run(self):
+ self.start = timeit.default_timer()
+ results = list(self.engine.process())
+ self.callback(results, self.finish(), self.i)
-class FileScanCounter(object):
- def __init__(self):
- self.ct = 0
- self.lock = threading.RLock()
+ def finish(self):
+ return round(timeit.default_timer() - self.start, 2)
- def __call__(self, filepath):
- self.increment()
-
- def __str__(self):
- with self.lock:
- return '%d' % self.ct
def increment(self):
with self.lock:
- self.ct += 1
+ self.i += 1
+ sublime.status_message("TodoReview: {0} files scanned".format(self.i))
- def reset(self):
- with self.lock:
- self.ct = 0
class TodoReviewCommand(sublime_plugin.TextCommand):
- def run(self, edit, paths=False, open_files=False, open_files_only=False):
- global settings
+
+ def run(self, edit, **args):
+ global settings, thread
filepaths = []
+ self.args = args
+ window = self.view.window()
settings = Settings(self.view)
- self.window = self.view.window()
+ paths = args.get('paths', None)
- if not paths:
- if settings.get('include_paths', False):
- paths = settings.get('include_paths', False)
+ if not paths and settings.get('include_paths', False):
+ paths = settings.get('include_paths')
- if open_files:
- filepaths = [view.file_name() for view in self.window.views() if view.file_name()]
+ if args.get('open_files', False):
+ filepaths = [v.file_name() for v in window.views() if v.file_name()]
- if not open_files_only:
+ if not args.get('open_files_only', False):
if not paths:
- paths = self.window.folders()
+ paths = window.folders()
else:
for p in paths:
- if path.isfile(p):
+ if os.path.isfile(p):
filepaths.append(p)
else:
paths = []
- file_counter = FileScanCounter()
- extractor = TodoExtractor(paths, filepaths, file_counter)
+ engine = Engine(paths, filepaths, self.view)
+ thread = Thread(engine, self.render)
+ thread.start()
- worker_thread = WorkerThread(extractor, self.render_formatted, file_counter)
- worker_thread.start()
- ThreadProgress(worker_thread, 'Finding TODOs', '', file_counter)
- def render_formatted(self, rendered, counter):
- self.window.run_command('render_result_run', {'formatted_results': rendered, 'file_counter': str(counter)})
+ def render(self, results, time, count):
+ self.view.run_command('todo_review_render', {
+ "results": results,
+ "time": time,
+ "count": count,
+ "args": self.args
+ })
-class NavigateResults(sublime_plugin.TextCommand):
- def __init__(self, view):
- super(NavigateResults, self).__init__(view)
-
- def run(self, edit, direction):
- view_settings = self.view.settings()
- results = self.view.get_regions('results')
-
- start_arr = {
- 'forward': -1,
- 'backward': 0,
- 'forward_skip': -1,
- 'backward_skip': 0
- }
-
- dir_arr = {
- 'forward': 1,
- 'backward': -1,
- 'forward_skip': settings.get('navigation_forward_skip', 10),
- 'backward_skip': settings.get('navigation_backward_skip', 10) * -1
- }
-
- if not results:
- sublime.status_message('No results to navigate')
+
+class TodoReviewRender(sublime_plugin.TextCommand):
+
+ def run(self, edit, results, time, count, args):
+ self.args = args
+ self.edit = edit
+ self.time = time
+ self.count = count
+ self.results = results
+ self.sorted = self.sort()
+ self.rview = self.get_view()
+
+ self.draw_header()
+ self.draw_results()
+
+ self.window.focus_view(self.rview)
+ self.rview.settings().set('review_args', self.args)
+
+
+ def sort(self):
+ self.largest = 0
+
+ for item in self.results:
+ self.largest = max(len(self.draw_file(item)), self.largest)
+
+ self.largest = min(self.largest, settings.get('render_maxspaces', 50)) + 6
+
+ w = settings.get('patterns_weight', {})
+ key = lambda m: (str(w.get(m['patt'].upper(), m['patt'])), m['priority'])
+
+ results = sorted(self.results, key=key)
+ return itertools.groupby(results, key=lambda m: m['patt'])
+
+
+ def get_view(self):
+ self.window = sublime.active_window()
+
+ for view in self.window.views():
+ if view.settings().get('todo_results', False):
+ view.erase(self.edit, sublime.Region(0, view.size()))
+ return view
+
+ view = self.window.new_file()
+ view.set_name('TodoReview')
+ view.set_scratch(True)
+ view.settings().set('todo_results', True)
+
+ view.assign_syntax('Packages/TodoReview/TodoReview.hidden-tmLanguage')
+ view.settings().set('line_padding_bottom', 2)
+ view.settings().set('line_padding_top', 2)
+ view.settings().set('word_wrap', False)
+ view.settings().set('command_mode', True)
+ return view
+
+
+ def draw_header(self):
+
+ forms = settings.get('render_header_format', '%d - %c files in %t secs')
+ datestr = settings.get('render_header_date', '%A %m/%d/%y at %I:%M%p')
+
+ if len(forms) == 0:
return
- selection = int(view_settings.get('selected_result', start_arr[direction]))
- selection = selection + dir_arr[direction]
+ date = datetime.datetime.now().strftime(datestr)
- try:
- target = results[selection]
- except IndexError:
- if selection < 0:
- target = results[0]
- selection = 0
- else:
- target = results[len(results) - 1]
- selection = len(results) - 1
+ res = '// '
+ res += forms \
+ .replace('%d', date) \
+ .replace('%t', str(self.time)) \
+ .replace('%c', str(self.count))
+ res += '\n'
+
+ self.rview.insert(self.edit, self.rview.size(), res)
+
+
+ def draw_results(self):
+ data = [x[:] for x in [[]] * 2]
+
+ for patt, items in self.sorted:
+ items = list(items)
+
+ res = '\n## %t (%n)\n' \
+ .replace('%t', patt.upper()) \
+ .replace('%n', str(len(items)))
+
+ self.rview.insert(self.edit, self.rview.size(), res)
- view_settings.set('selected_result', selection)
- target = target.cover(target)
- self.view.add_regions('selection', [target], 'selected', 'dot')
- target.b = target.a + 5
- self.view.show(target)
+ for idx, item in enumerate(items, 1):
-class ClearSelection(sublime_plugin.TextCommand):
- def run(self, edit):
- self.view.erase_regions('selection')
- self.view.settings().erase('selected_result')
+ line = '%i. %f' \
+ .replace('%i', str(idx)) \
+ .replace('%f', self.draw_file(item))
-class GotoComment(sublime_plugin.TextCommand):
- def __init__(self, *args):
- super(GotoComment, self).__init__(*args)
+ res = '%f%s%n\n' \
+ .replace('%f', line) \
+ .replace('%s', ' '*max((self.largest - len(line)), 1)) \
+ .replace('%n', item['note'])
- def run(self, edit):
- selection = int(self.view.settings().get('selected_result', -1))
- selected_region = self.view.get_regions('results')[selection]
+ start = self.rview.size()
+ self.rview.insert(self.edit, start, res)
+ region = sublime.Region(start, self.rview.size())
- data = self.view.settings().get('result_regions')['{0},{1}'.format(selected_region.a, selected_region.b)]
- new_view = self.view.window().open_file(data['filepath'])
- do_when(lambda: not new_view.is_loading(), lambda:new_view.run_command('goto_line', {'line': data['linenum']}))
+ data[0].append(region)
+ data[1].append(item)
-class MouseGotoComment(sublime_plugin.TextCommand):
- def __init__(self, *args):
- super(MouseGotoComment, self).__init__(*args)
+ self.rview.add_regions('results', data[0], '')
- def run(self, edit):
- if not self.view.settings().get('result_regions'):
- return
+ d = dict(('{0},{1}'.format(k.a, k.b), v) for k, v in zip(data[0], data[1]))
+ self.rview.settings().set('review_results', d)
- result = self.view.line(self.view.sel()[0].end())
- target = result.cover(result)
- self.view.add_regions('selection', [target], 'selected', 'dot')
- self.view.show(target)
+ def draw_file(self, item):
+ if settings.get('render_include_folder', False):
+ f = os.path.dirname(item['file']).replace('\\', '/').split('/')
+ f = f[len(f) - 1] + '/' + os.path.basename(item['file'])
+ else:
+ f = os.path.basename(item['file'])
+
+ return '%f:%l' \
+ .replace('%f', f) \
+ .replace('%l', str(item['line']))
+
+
+class TodoReviewResults(sublime_plugin.TextCommand):
+
+ def run(self, edit, **args):
+ self.settings = self.view.settings()
+
+ if not self.settings.get('review_results'):
+ return
+
+ if args.get('open'):
+ window = self.view.window()
+ index = int(self.settings.get('selected_result', -1))
+ result = self.view.get_regions('results')[index]
+
+ coords = '{0},{1}'.format(result.a, result.b)
+ i = self.settings.get('review_results')[coords]
+ p = "%f:%l".replace('%f', i['file']).replace('%l', str(i['line']))
+ view = window.open_file(p, sublime.ENCODED_POSITION)
+ window.focus_view(view)
+ return
+
+ if args.get('refresh'):
+ args = self.settings.get('review_args')
+ self.view.run_command('todo_review', args)
+ self.settings.erase('selected_result')
+ return
+
+ if args.get('direction'):
+ d = args.get('direction')
+ results = self.view.get_regions('results')
- data = self.view.settings().get('result_regions')['{0},{1}'.format(result.a, result.b)]
- new_view = self.view.window().open_file(data['filepath'])
- do_when(lambda: not new_view.is_loading(), lambda: new_view.run_command("goto_line", {"line": data['linenum']}))
\ No newline at end of file
+ if not results:
+ return
+
+ start_arr = {
+ 'down': -1,
+ 'up': 0,
+ 'down_skip': -1,
+ 'up_skip': 0
+ }
+
+ dir_arr = {
+ 'down': 1,
+ 'up': -1,
+ 'down_skip': settings.get('navigation_forward_skip', 10),
+ 'up_skip': settings.get('navigation_backward_skip', 10) * -1
+ }
+
+ sel = int(self.settings.get('selected_result', start_arr[d]))
+ sel = sel + dir_arr[d]
+
+ if sel == -1:
+ target = results[len(results) - 1]
+ sel = len(results) - 1
+
+ if sel < 0:
+ target = results[0]
+ sel = 0
+
+ if sel >= len(results):
+ target = results[0]
+ sel = 0
+
+ target = results[sel]
+ self.settings.set('selected_result', sel)
+
+ region = target.cover(target)
+ self.view.add_regions('selection', [region], 'selected', 'dot')
+ region.b = region.a + 5
+ self.view.show(region)
+ return
diff --git a/TodoReview.sublime-settings b/TodoReview.sublime-settings
index 1b2b0a5..f6c00b5 100644
--- a/TodoReview.sublime-settings
+++ b/TodoReview.sublime-settings
@@ -1,6 +1,8 @@
{
"patterns": {
- "TODO": "TODO[\\s]*?:+(?P.*)$"
+ "TODO": "TODO[\\s]*?:[\\s]*(?P.*)$"
+ },
+ "patterns_weight": {
},
"exclude_folders": [
"*.git*"
@@ -11,7 +13,9 @@
],
"case_sensitive": false,
"render_include_folder": false,
- "render_spaces": 1,
+ "render_maxspaces": 50,
+ "render_header_format": "%d - %c files in %t secs",
+ "render_header_date": "%A %m/%d/%y at %I:%M%p",
"navigation_forward_skip" : 10,
"navigation_backward_skip" : 10
}
\ No newline at end of file
diff --git a/messages.json b/messages.json
index ec4dbcd..81533f6 100644
--- a/messages.json
+++ b/messages.json
@@ -1,11 +1,12 @@
{
- "0.1.0": "messages/0.1.0.txt",
- "2.0.0": "messages/2.0.0.txt",
- "2.1.0": "messages/2.1.0.txt",
- "2.1.1": "messages/2.1.1.txt",
- "2.1.2": "messages/2.1.2.txt",
- "2.1.3": "messages/2.1.3.txt",
- "2.1.4": "messages/2.1.4.txt",
- "2.1.5": "messages/2.1.5.txt",
- "2.1.6": "messages/2.1.6.txt"
+ "0.1.0": "messages/0.1.0.txt",
+ "2.0.0": "messages/2.0.0.txt",
+ "2.1.0": "messages/2.1.0.txt",
+ "2.1.1": "messages/2.1.1.txt",
+ "2.1.2": "messages/2.1.2.txt",
+ "2.1.3": "messages/2.1.3.txt",
+ "2.1.4": "messages/2.1.4.txt",
+ "2.1.5": "messages/2.1.5.txt",
+ "2.1.6": "messages/2.1.6.txt",
+ "3.0.0": "messages/3.0.0.txt"
}
diff --git a/messages/3.0.0.txt b/messages/3.0.0.txt
new file mode 100644
index 0000000..a864953
--- /dev/null
+++ b/messages/3.0.0.txt
@@ -0,0 +1,51 @@
+TodoReview 3.0.0
+--------------------
+
+Documentation is at https://github.com/jonathandelgado/SublimeTodoReview
+As always, please report any issues.
+
+This is a huge update, great ready for some fantastic features!
+
+Some quick bullets:
+- Complete recode - order of magnitude faster
+- Press `r` in report view to refresh the report (using the same arguments)
+- For open files, TodoReview now uses the buffer (yes, unsaved changes are searched)
+- Sort patterns in report by a specific weight
+- New and configurable header
+- Report spacing (between files and notes) is now automatically aligned
+- Timer for each search
+- Non UTF-8 File support
+
+Some breaking changes:
+- `render_spaces` no longer works, use `render_maxspaces` instead
+- Navigation arguments have changed from "forward" and "backward" to "up" and "down"
+- Mousemaps were removed as they are incompatible with the new engine
+
+
+This update was a long time coming. A lot of the features were previously mentioned as upcoming, but were put off due to some code fragmentation. The original project, SublimeTODO, had a much different threading system due to using Python 2. Now that we are in Python 3, I decided to do a full rewrite for not only speed improvements, but allowing additional features to be added with greater ease.
+
+Some cool new features to be aware of: you can now press `r` in the report view to refresh the report. This uses the original arguments, so if you just scanned one file and press `r`, it will only scan that one file again. TodoReview now uses the SublimeText buffer for scanning currently opened files. This is a big feature for two reasons; allowing unsaved changes to be scanned, and skipping a lot of disk IO. Please keep in mind that files that have never been saved to disk, thus don't have a directory in SublimeText will be skipped as normal.
+
+Reports have gotten a complete overhaul, aside from the new refresh feature. The header is now completely configurable and removable. Patterns can be weighed as you like using the new `patterns_weight` setting. Spacing between files and notes are now controlled by an an automatic alignment function, thus deprecating `render_spaces`, but a new setting `render_maxspaces` has been added to manipulate the engine if needed. Also, a timer, if you would like to feel the new and improved speed, has been added to the default report header.
+
+
+# 3.0.0 - 9/30/14
+- Add: Syntax highlighting for report header
+- Add: Search Timer
+- Add: Use buffer if already open
+- Fix: Opening comment to line (#66)
+- Fix: Non UTF-8 Files (#64)
+- Doc: New features
+- Doc: Outlined arguments (#63)
+- Add: `patterns_weight` option (#58)
+- Add: `render_max_spaces` option (#59)
+- Del: `render_spaces` in favor of maxspaces (#59)
+- Add: `render_header_date` option
+- Add: `render_header_format` option
+- Upd: Report Header
+- Add: Refresh function (#60)
+- Upd: Changed direction arguments
+- Del: Clear keybinding
+- Del: Mousemaps
+- Upd: Refactored
+- Upd: Combined keymaps
\ No newline at end of file
diff --git a/readme.md b/readme.md
index 3e00f1f..e4aac73 100644
--- a/readme.md
+++ b/readme.md
@@ -165,10 +165,10 @@ if you ever feel the overwhelming urge to either remove or edit the standard rep
"render_header_date": "%A %m/%d/%y at %I:%M%p"
```
-- **%d** is the formatted date string
-- **%c** is the total file count
-- **%t** is the total time count
-- The date formatting can be found on the [Python Documentation](https://docs.python.org/2/library/datetime.html)
+- **%d** - the formatted date string
+- **%c** - the total file count
+- **%t** - the total time count
+- The date formatting can be found in the [Python Documentation](https://docs.python.org/2/library/datetime.html)
## Custom Skip Lines
If you would like to skip more (or less) than 10 lines at a time when using `page up` or `page down`, we have a setting for you! These defaults to `10`.
@@ -181,9 +181,9 @@ If you would like to skip more (or less) than 10 lines at a time when using `pag
# Arguments
The TodoReview search engine takes a number of arguments to better find what you are looking for. These are automatically generated on a number of sugar functions, such as using the sidebar or command pallet, but you can also create your own keybinds to utilize them.
-- **`paths`** - An array of paths to search
-- **`open_files`** - boolean to include open files
-- **`open_files_only`** - boolean to restrict search to open files
+- `paths` - An array of paths to search
+- `open_files` - Boolean to include open files
+- `open_files_only` - Boolean to restrict search to open files
# License