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'
|
SELECT_PATCHSETS = 'select patchsets'
|
||||||
NEXT_SELECTABLE = 'next selectable'
|
NEXT_SELECTABLE = 'next selectable'
|
||||||
PREV_SELECTABLE = 'prev selectable'
|
PREV_SELECTABLE = 'prev selectable'
|
||||||
|
INTERACTIVE_SEARCH = 'interactive search'
|
||||||
|
|
||||||
DEFAULT_KEYMAP = {
|
DEFAULT_KEYMAP = {
|
||||||
REDRAW_SCREEN: 'ctrl l',
|
REDRAW_SCREEN: 'ctrl l',
|
||||||
@ -129,7 +130,8 @@ DEFAULT_KEYMAP = {
|
|||||||
SELECT_PATCHSETS: 'p',
|
SELECT_PATCHSETS: 'p',
|
||||||
NEXT_SELECTABLE: 'tab',
|
NEXT_SELECTABLE: 'tab',
|
||||||
PREV_SELECTABLE: 'shift tab',
|
PREV_SELECTABLE: 'shift tab',
|
||||||
}
|
INTERACTIVE_SEARCH: 'ctrl s',
|
||||||
|
}
|
||||||
|
|
||||||
URWID_COMMANDS = frozenset((
|
URWID_COMMANDS = frozenset((
|
||||||
urwid.REDRAW_SCREEN,
|
urwid.REDRAW_SCREEN,
|
||||||
|
@ -193,6 +193,52 @@ class YesNoDialog(ButtonDialog):
|
|||||||
return None
|
return None
|
||||||
return r
|
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):
|
class HyperText(urwid.Text):
|
||||||
_selectable = True
|
_selectable = True
|
||||||
|
|
||||||
|
@ -47,6 +47,7 @@ DEFAULT_PALETTE={
|
|||||||
'comment-name': ['white', 'dark gray'],
|
'comment-name': ['white', 'dark gray'],
|
||||||
'line-number': ['dark gray', ''],
|
'line-number': ['dark gray', ''],
|
||||||
'focused-line-number': ['dark gray,standout', ''],
|
'focused-line-number': ['dark gray,standout', ''],
|
||||||
|
'search-result': ['default,standout', ''],
|
||||||
# Change view
|
# Change view
|
||||||
'change-data': ['dark cyan', ''],
|
'change-data': ['dark cyan', ''],
|
||||||
'focused-change-data': ['light cyan', ''],
|
'focused-change-data': ['light cyan', ''],
|
||||||
|
@ -106,10 +106,16 @@ class BaseDiffLine(urwid.Button):
|
|||||||
def selectable(self):
|
def selectable(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def search(self, search, attribute):
|
||||||
|
pass
|
||||||
|
|
||||||
class BaseFileHeader(urwid.Button):
|
class BaseFileHeader(urwid.Button):
|
||||||
def selectable(self):
|
def selectable(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def search(self, search, attribute):
|
||||||
|
pass
|
||||||
|
|
||||||
class BaseFileReminder(urwid.WidgetWrap):
|
class BaseFileReminder(urwid.WidgetWrap):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -170,6 +176,7 @@ class BaseDiffView(urwid.WidgetWrap):
|
|||||||
|
|
||||||
def _init(self):
|
def _init(self):
|
||||||
del self._w.contents[:]
|
del self._w.contents[:]
|
||||||
|
self.search = None
|
||||||
with self.app.db.getSession() as session:
|
with self.app.db.getSession() as session:
|
||||||
new_revision = session.getRevision(self.new_revision_key)
|
new_revision = session.getRevision(self.new_revision_key)
|
||||||
old_comments = []
|
old_comments = []
|
||||||
@ -427,7 +434,26 @@ class BaseDiffView(urwid.WidgetWrap):
|
|||||||
context = item.context
|
context = item.context
|
||||||
return 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):
|
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
|
old_focus = self.listbox.focus
|
||||||
r = super(BaseDiffView, self).keypress(size, key)
|
r = super(BaseDiffView, self).keypress(size, key)
|
||||||
new_focus = self.listbox.focus
|
new_focus = self.listbox.focus
|
||||||
@ -446,6 +472,10 @@ class BaseDiffView(urwid.WidgetWrap):
|
|||||||
if keymap.SELECT_PATCHSETS in commands:
|
if keymap.SELECT_PATCHSETS in commands:
|
||||||
self.openPatchsetDialog()
|
self.openPatchsetDialog()
|
||||||
return None
|
return None
|
||||||
|
if keymap.INTERACTIVE_SEARCH in commands:
|
||||||
|
self.search = ''
|
||||||
|
self.interactiveSearch(self.search)
|
||||||
|
return None
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def mouse_event(self, size, event, button, x, y, focus):
|
def mouse_event(self, size, event, button, x, y, focus):
|
||||||
@ -515,3 +545,10 @@ class BaseDiffView(urwid.WidgetWrap):
|
|||||||
self.app.backScreen()
|
self.app.backScreen()
|
||||||
self.old_revision_key, self.new_revision_key = dialog.getSelected()
|
self.old_revision_key, self.new_revision_key = dialog.getSelected()
|
||||||
self._init()
|
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):
|
def __init__(self, app, context, old, new, callback=None):
|
||||||
super(SideDiffLine, self).__init__('', on_press=callback)
|
super(SideDiffLine, self).__init__('', on_press=callback)
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.text_widgets = []
|
||||||
columns = []
|
columns = []
|
||||||
for (ln, action, line) in (old, new):
|
for (ln, action, line) in (old, new):
|
||||||
if ln is None:
|
if ln is None:
|
||||||
@ -90,7 +91,9 @@ class SideDiffLine(BaseDiffLine):
|
|||||||
ln = '%*i' % (LN_COL_WIDTH-1, ln)
|
ln = '%*i' % (LN_COL_WIDTH-1, ln)
|
||||||
ln_col = urwid.Text(('line-number', ln))
|
ln_col = urwid.Text(('line-number', ln))
|
||||||
ln_col.set_wrap_mode('clip')
|
ln_col.set_wrap_mode('clip')
|
||||||
line_col = urwid.Text(line)
|
line_col = mywid.SearchableText(line)
|
||||||
|
self.text_widgets.append(line_col)
|
||||||
|
|
||||||
if action == '':
|
if action == '':
|
||||||
line_col = urwid.AttrMap(line_col, 'nonexistent')
|
line_col = urwid.AttrMap(line_col, 'nonexistent')
|
||||||
columns += [(LN_COL_WIDTH, ln_col), line_col]
|
columns += [(LN_COL_WIDTH, ln_col), line_col]
|
||||||
@ -105,6 +108,10 @@ class SideDiffLine(BaseDiffLine):
|
|||||||
}
|
}
|
||||||
self._w = urwid.AttrMap(col, None, focus_map=map)
|
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):
|
class SideFileHeader(BaseFileHeader):
|
||||||
def __init__(self, app, context, old, new, callback=None):
|
def __init__(self, app, context, old, new, callback=None):
|
||||||
super(SideFileHeader, self).__init__('', on_press=callback)
|
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)]
|
columns = [(LN_COL_WIDTH, urwid.Text(u'')), (LN_COL_WIDTH, new_ln_col)]
|
||||||
if new_action == ' ':
|
if new_action == ' ':
|
||||||
columns = [(LN_COL_WIDTH, old_ln_col), (LN_COL_WIDTH, new_ln_col)]
|
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 == '':
|
if action == '':
|
||||||
line_col = urwid.AttrMap(line_col, 'nonexistent')
|
line_col = urwid.AttrMap(line_col, 'nonexistent')
|
||||||
columns += [line_col]
|
columns += [line_col]
|
||||||
@ -88,6 +89,9 @@ class UnifiedDiffLine(BaseDiffLine):
|
|||||||
}
|
}
|
||||||
self._w = urwid.AttrMap(col, None, focus_map=map)
|
self._w = urwid.AttrMap(col, None, focus_map=map)
|
||||||
|
|
||||||
|
def search(self, search, attribute):
|
||||||
|
self.text_widget.search(search, attribute)
|
||||||
|
|
||||||
class UnifiedFileHeader(BaseFileHeader):
|
class UnifiedFileHeader(BaseFileHeader):
|
||||||
def __init__(self, app, context, oldnew, old, new, callback=None):
|
def __init__(self, app, context, oldnew, old, new, callback=None):
|
||||||
super(UnifiedFileHeader, self).__init__('', on_press=callback)
|
super(UnifiedFileHeader, self).__init__('', on_press=callback)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user