diff --git a/gertty/mywid.py b/gertty/mywid.py index 02dab27..efa1f95 100644 --- a/gertty/mywid.py +++ b/gertty/mywid.py @@ -290,6 +290,62 @@ class SearchableText(urwid.Text): self._invalidate() return found +class Searchable(object): + def searchInit(self): + self.search = None + self.results = [] + self.current_result = 0 + + def searchValidChar(self, ch): + return urwid.util.is_wide_char(ch, 0) or (len(ch) == 1 and ord(ch) >= 32) + + def searchKeypress(self, size, key): + if self.search is not None: + if self.searchValidChar(key) or key == 'backspace': + if key == 'backspace': + self.search = self.search[:-1] + else: + self.search += key + self.interactiveSearch(self.search) + return True + else: + commands = self.app.config.keymap.getCommands([key]) + if keymap.INTERACTIVE_SEARCH in commands: + self.nextSearchResult() + return True + else: + self.app.status.update(title=self.title) + if not self.search: + self.interactiveSearch(None) + self.search = None + if key in ['enter', 'esc']: + return True + return False + + def searchStart(self): + self.search = '' + self.app.status.update(title=("Search: ")) + + def interactiveSearch(self, search): + if search is not None: + self.app.status.update(title=("Search: " + search)) + self.results = [] + self.current_result = 0 + for i, line in enumerate(self.listbox.body): + if hasattr(line, 'search'): + if line.search(search, 'search-result'): + self.results.append(i) + + def nextSearchResult(self): + if not self.results: + return + dest = self.results[self.current_result] + self.listbox.set_focus(dest) + self.listbox._invalidate() + self.current_result += 1 + if self.current_result >= len(self.results): + self.current_result = 0 + class HyperText(urwid.Text): _selectable = True diff --git a/gertty/view/change_list.py b/gertty/view/change_list.py index 19868cf..8035cce 100644 --- a/gertty/view/change_list.py +++ b/gertty/view/change_list.py @@ -111,13 +111,13 @@ class ChangeRow(urwid.Button, ChangeListColumns): self.change_key = change.key self.prefix = prefix self.enabled_columns = enabled_columns - self.subject = urwid.Text(u'', wrap='clip') - self.number = urwid.Text(u'') - self.updated = urwid.Text(u'') - self.project = urwid.Text(u'', wrap='clip') - self.owner = urwid.Text(u'', wrap='clip') - self.branch = urwid.Text(u'', wrap='clip') - self.topic = urwid.Text(u'', wrap='clip') + self.subject = mywid.SearchableText(u'', wrap='clip') + self.number = mywid.SearchableText(u'') + self.updated = mywid.SearchableText(u'') + self.project = mywid.SearchableText(u'', wrap='clip') + self.owner = mywid.SearchableText(u'', wrap='clip') + self.branch = mywid.SearchableText(u'', wrap='clip') + self.topic = mywid.SearchableText(u'', wrap='clip') self.mark = False self.columns = urwid.Columns([], dividechars=1) self.row_style = urwid.AttrMap(self.columns, '') @@ -125,6 +125,23 @@ class ChangeRow(urwid.Button, ChangeListColumns): self.category_columns = [] self.update(change, categories) + def search(self, search, attribute): + if self.subject.search(search, attribute): + return True + if self.number.search(search, attribute): + return True + if self.project.search(search, attribute): + return True + if self.branch.search(search, attribute): + return True + if self.owner.search(search, attribute): + return True + if self.topic.search(search, attribute): + return True + if self.updated.search(search, attribute): + return True + return False + def update(self, change, categories): if change.reviewed or change.hidden: style = 'reviewed-change' @@ -181,7 +198,6 @@ class ChangeRow(urwid.Button, ChangeListColumns): self.columns.options('given', 2))) self.updateColumns() - class ChangeListHeader(urwid.WidgetWrap, ChangeListColumns): def __init__(self, enabled_columns): self.enabled_columns = enabled_columns @@ -205,7 +221,7 @@ class ChangeListHeader(urwid.WidgetWrap, ChangeListColumns): @mouse_scroll_decorator.ScrollByWheel -class ChangeListView(urwid.WidgetWrap): +class ChangeListView(urwid.WidgetWrap, mywid.Searchable): required_columns = set(['Number', 'Subject', 'Updated']) optional_columns = set(['Topic', 'Branch']) @@ -249,6 +265,8 @@ class ChangeListView(urwid.WidgetWrap): "Reverse the sort"), (keymap.LOCAL_CHERRY_PICK, "Cherry-pick the most recent revision of the selected change onto the local repo"), + (keymap.INTERACTIVE_SEARCH, + "Interactive search"), ] def help(self): @@ -260,6 +278,7 @@ class ChangeListView(urwid.WidgetWrap): unreviewed=False, sort_by=None, reverse=None): super(ChangeListView, self).__init__(urwid.Pile([])) self.log = logging.getLogger('gertty.view.change_list') + self.searchInit() self.app = app self.query = query self.query_desc = query_desc or query @@ -521,6 +540,9 @@ class ChangeListView(urwid.WidgetWrap): self.listbox.focus_position = pos def keypress(self, size, key): + if self.searchKeypress(size, key): + return None + if not self.app.input_buffer: key = super(ChangeListView, self).keypress(size, key) keys = self.app.input_buffer + [key] @@ -681,6 +703,9 @@ class ChangeListView(urwid.WidgetWrap): if keymap.RESTORE_CHANGE in commands: self.restoreChange() return True + if keymap.INTERACTIVE_SEARCH in commands: + self.searchStart() + return True return False def onSelect(self, button, change_key): diff --git a/gertty/view/diff.py b/gertty/view/diff.py index 1bb7cde..ec4285d 100644 --- a/gertty/view/diff.py +++ b/gertty/view/diff.py @@ -156,7 +156,7 @@ class DiffContextButton(urwid.WidgetWrap): self.view.expandChunk(self.diff, self.chunk, from_end=-10) @mouse_scroll_decorator.ScrollByWheel -class BaseDiffView(urwid.WidgetWrap): +class BaseDiffView(urwid.WidgetWrap, mywid.Searchable): def getCommands(self): return [ (keymap.ACTIVATE, @@ -182,9 +182,7 @@ class BaseDiffView(urwid.WidgetWrap): def _init(self): del self._w.contents[:] - self.search = None - self.results = [] - self.current_result = None + self.searchInit() with self.app.db.getSession() as session: new_revision = session.getRevision(self.new_revision_key) old_comments = [] @@ -443,30 +441,9 @@ class BaseDiffView(urwid.WidgetWrap): context = item.context return context - def search_valid_char(self, ch): - return urwid.util.is_wide_char(ch, 0) or (len(ch) == 1 and ord(ch) >= 32) - def keypress(self, size, key): - if self.search is not None: - if self.search_valid_char(key) or key == 'backspace': - if key == 'backspace': - self.search = self.search[:-1] - else: - self.search += key - self.interactiveSearch(self.search) - return None - else: - commands = self.app.config.keymap.getCommands([key]) - if keymap.INTERACTIVE_SEARCH in commands: - self.nextSearchResult() - return None - else: - self.app.status.update(title=self.title) - if not self.search: - self.interactiveSearch(None) - self.search = None - if key in ['enter', 'esc']: - return None + if self.searchKeypress(size, key): + return None old_focus = self.listbox.focus if not self.app.input_buffer: @@ -489,8 +466,7 @@ class BaseDiffView(urwid.WidgetWrap): self.openPatchsetDialog() return None if keymap.INTERACTIVE_SEARCH in commands: - self.search = '' - self.app.status.update(title=("Search: ")) + self.searchStart() return None return key @@ -561,23 +537,3 @@ class BaseDiffView(urwid.WidgetWrap): self.app.backScreen() self.old_revision_key, self.new_revision_key = dialog.getSelected() self._init() - - def interactiveSearch(self, search): - if search is not None: - self.app.status.update(title=("Search: " + search)) - self.results = [] - self.current_result = 0 - for i, line in enumerate(self.listbox.body): - if hasattr(line, 'search'): - if line.search(search, 'search-result'): - self.results.append(i) - - def nextSearchResult(self): - if not self.results: - return - dest = self.results[self.current_result] - self.listbox.set_focus(dest) - self.listbox._invalidate() - self.current_result += 1 - if self.current_result >= len(self.results): - self.current_result = 0