Add interactive search to diff view
Add a simple interactive search to the diff view. This is bound to C-s by default. It highlights text, but does not yet navigate. Change-Id: Ic795bb5d18911590237b6595f812e10fd4baa1ce
This commit is contained in:
parent
c8d81b7693
commit
0d0f0f6dcd
@ -73,6 +73,7 @@ TOGGLE_SUBSCRIBED = 'toggle subscribed'
|
||||
SELECT_PATCHSETS = 'select patchsets'
|
||||
NEXT_SELECTABLE = 'next selectable'
|
||||
PREV_SELECTABLE = 'prev selectable'
|
||||
INTERACTIVE_SEARCH = 'interactive search'
|
||||
|
||||
DEFAULT_KEYMAP = {
|
||||
REDRAW_SCREEN: 'ctrl l',
|
||||
@ -129,7 +130,8 @@ DEFAULT_KEYMAP = {
|
||||
SELECT_PATCHSETS: 'p',
|
||||
NEXT_SELECTABLE: 'tab',
|
||||
PREV_SELECTABLE: 'shift tab',
|
||||
}
|
||||
INTERACTIVE_SEARCH: 'ctrl s',
|
||||
}
|
||||
|
||||
URWID_COMMANDS = frozenset((
|
||||
urwid.REDRAW_SCREEN,
|
||||
|
@ -193,6 +193,52 @@ class YesNoDialog(ButtonDialog):
|
||||
return None
|
||||
return r
|
||||
|
||||
class SearchableText(urwid.Text):
|
||||
def set_text(self, markup):
|
||||
self._markup = markup
|
||||
super(SearchableText, self).set_text(markup)
|
||||
|
||||
def search(self, search, attribute):
|
||||
if not search:
|
||||
self.set_text(self._markup)
|
||||
return
|
||||
(text, attrs) = urwid.util.decompose_tagmarkup(self._markup)
|
||||
last = 0
|
||||
while True:
|
||||
start = text.find(search, last)
|
||||
if start < 0:
|
||||
break
|
||||
end = start + len(search)
|
||||
i = 0
|
||||
newattrs = []
|
||||
for attr, al in attrs:
|
||||
if i + al <= start:
|
||||
i += al
|
||||
newattrs.append((attr, al))
|
||||
continue
|
||||
if i >= end:
|
||||
i += al
|
||||
newattrs.append((attr, al))
|
||||
continue
|
||||
before = max(start - i, 0)
|
||||
after = max(i + al - end, 0)
|
||||
if before:
|
||||
newattrs.append((attr, before))
|
||||
newattrs.append((attribute, len(search)))
|
||||
if after:
|
||||
newattrs.append((attr, after))
|
||||
i += al
|
||||
if i < start:
|
||||
newattrs.append((None, start-i))
|
||||
i += start-i
|
||||
if i < end:
|
||||
newattrs.append((attribute, len(search)))
|
||||
last = start + 1
|
||||
attrs = newattrs
|
||||
self._text = text
|
||||
self._attrib = attrs
|
||||
self._invalidate()
|
||||
|
||||
class HyperText(urwid.Text):
|
||||
_selectable = True
|
||||
|
||||
|
@ -47,6 +47,7 @@ DEFAULT_PALETTE={
|
||||
'comment-name': ['white', 'dark gray'],
|
||||
'line-number': ['dark gray', ''],
|
||||
'focused-line-number': ['dark gray,standout', ''],
|
||||
'search-result': ['default,standout', ''],
|
||||
# Change view
|
||||
'change-data': ['dark cyan', ''],
|
||||
'focused-change-data': ['light cyan', ''],
|
||||
|
@ -106,10 +106,16 @@ class BaseDiffLine(urwid.Button):
|
||||
def selectable(self):
|
||||
return True
|
||||
|
||||
def search(self, search, attribute):
|
||||
pass
|
||||
|
||||
class BaseFileHeader(urwid.Button):
|
||||
def selectable(self):
|
||||
return True
|
||||
|
||||
def search(self, search, attribute):
|
||||
pass
|
||||
|
||||
class BaseFileReminder(urwid.WidgetWrap):
|
||||
pass
|
||||
|
||||
@ -170,6 +176,7 @@ class BaseDiffView(urwid.WidgetWrap):
|
||||
|
||||
def _init(self):
|
||||
del self._w.contents[:]
|
||||
self.search = None
|
||||
with self.app.db.getSession() as session:
|
||||
new_revision = session.getRevision(self.new_revision_key)
|
||||
old_comments = []
|
||||
@ -427,7 +434,26 @@ 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:
|
||||
self.app.status.update(title=self.title)
|
||||
if not self.search:
|
||||
self.interactiveSearch(None)
|
||||
self.search = None
|
||||
if key in ['enter', 'esc']:
|
||||
return None
|
||||
|
||||
old_focus = self.listbox.focus
|
||||
r = super(BaseDiffView, self).keypress(size, key)
|
||||
new_focus = self.listbox.focus
|
||||
@ -446,6 +472,10 @@ class BaseDiffView(urwid.WidgetWrap):
|
||||
if keymap.SELECT_PATCHSETS in commands:
|
||||
self.openPatchsetDialog()
|
||||
return None
|
||||
if keymap.INTERACTIVE_SEARCH in commands:
|
||||
self.search = ''
|
||||
self.interactiveSearch(self.search)
|
||||
return None
|
||||
return r
|
||||
|
||||
def mouse_event(self, size, event, button, x, y, focus):
|
||||
@ -515,3 +545,10 @@ 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))
|
||||
for line in self.listbox.body:
|
||||
if hasattr(line, 'search'):
|
||||
line.search(search, 'search-result')
|
||||
|
@ -82,6 +82,7 @@ class SideDiffLine(BaseDiffLine):
|
||||
def __init__(self, app, context, old, new, callback=None):
|
||||
super(SideDiffLine, self).__init__('', on_press=callback)
|
||||
self.context = context
|
||||
self.text_widgets = []
|
||||
columns = []
|
||||
for (ln, action, line) in (old, new):
|
||||
if ln is None:
|
||||
@ -90,7 +91,9 @@ class SideDiffLine(BaseDiffLine):
|
||||
ln = '%*i' % (LN_COL_WIDTH-1, ln)
|
||||
ln_col = urwid.Text(('line-number', ln))
|
||||
ln_col.set_wrap_mode('clip')
|
||||
line_col = urwid.Text(line)
|
||||
line_col = mywid.SearchableText(line)
|
||||
self.text_widgets.append(line_col)
|
||||
|
||||
if action == '':
|
||||
line_col = urwid.AttrMap(line_col, 'nonexistent')
|
||||
columns += [(LN_COL_WIDTH, ln_col), line_col]
|
||||
@ -105,6 +108,10 @@ class SideDiffLine(BaseDiffLine):
|
||||
}
|
||||
self._w = urwid.AttrMap(col, None, focus_map=map)
|
||||
|
||||
def search(self, search, attribute):
|
||||
for w in self.text_widgets:
|
||||
w.search(search, attribute)
|
||||
|
||||
class SideFileHeader(BaseFileHeader):
|
||||
def __init__(self, app, context, old, new, callback=None):
|
||||
super(SideFileHeader, self).__init__('', on_press=callback)
|
||||
|
@ -73,7 +73,8 @@ class UnifiedDiffLine(BaseDiffLine):
|
||||
columns = [(LN_COL_WIDTH, urwid.Text(u'')), (LN_COL_WIDTH, new_ln_col)]
|
||||
if new_action == ' ':
|
||||
columns = [(LN_COL_WIDTH, old_ln_col), (LN_COL_WIDTH, new_ln_col)]
|
||||
line_col = urwid.Text(line)
|
||||
line_col = mywid.SearchableText(line)
|
||||
self.text_widget = line_col
|
||||
if action == '':
|
||||
line_col = urwid.AttrMap(line_col, 'nonexistent')
|
||||
columns += [line_col]
|
||||
@ -88,6 +89,9 @@ class UnifiedDiffLine(BaseDiffLine):
|
||||
}
|
||||
self._w = urwid.AttrMap(col, None, focus_map=map)
|
||||
|
||||
def search(self, search, attribute):
|
||||
self.text_widget.search(search, attribute)
|
||||
|
||||
class UnifiedFileHeader(BaseFileHeader):
|
||||
def __init__(self, app, context, oldnew, old, new, callback=None):
|
||||
super(UnifiedFileHeader, self).__init__('', on_press=callback)
|
||||
|
Loading…
x
Reference in New Issue
Block a user