Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: More find in files improvements #4599

Merged
merged 15 commits into from
Jun 22, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 111 additions & 65 deletions spyder/widgets/findinfiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from spyder.config.base import _
from spyder.py3compat import getcwd, to_text_string
from spyder.utils import icon_manager as ima
from spyder.utils.qthelpers import create_toolbutton, get_filetype_icon
from spyder.utils.qthelpers import create_toolbutton
from spyder.utils.encoding import is_text_file
from spyder.widgets.comboboxes import PatternComboBox
from spyder.widgets.onecolumntree import OneColumnTree
Expand All @@ -43,6 +43,10 @@
from spyder.widgets.waitingspinner import QWaitingSpinner


ON = 'on'
OFF = 'off'


class SearchThread(QThread):
"""Find in files search thread"""
sig_finished = Signal(bool)
Expand Down Expand Up @@ -126,50 +130,9 @@ def find_files_in_path(self, path):
return False
return True

def truncate_result(self, line, start, end):
ellipsis = '...'

left, match, right = line[:start], line[start:end], line[end:]
max_line_length = 40
offset = (len(line) - len(match)) // 2

left = left.split(' ')
num_left_words = len(left)

if num_left_words == 1:
left = left[0]
if len(left) > max_line_length:
left = ellipsis + left[-offset:]
left = [left]

right = right.split(' ')
num_right_words = len(right)

if num_right_words == 1:
right = right[0]
if len(right) > max_line_length:
right = right[:offset] + ellipsis
right = [right]

left = left[-3:]
right = right[:3]

if len(left) < num_left_words:
left = [ellipsis] + left

if len(right) < num_right_words:
right = right + [ellipsis]

left = ' '.join(left)
right = ' '.join(right)

trunc_line = '{0}<b>{1}</b>{2}'.format(left, match, right)
return trunc_line

def find_string_in_file(self, fname):
self.error_flag = False
self.sig_current_file.emit(fname)
# results = {}
try:
for lineno, line in enumerate(open(fname, 'rb')):
for text, enc in self.texts:
Expand All @@ -187,25 +150,20 @@ def find_string_in_file(self, fname):
line_dec = line
if self.text_re:
for match in re.finditer(text, line):
displ_line = self.truncate_result(line_dec,
match.start(),
match.end())
self.total_matches += 1
self.sig_file_match.emit((osp.abspath(fname),
lineno + 1,
match.start(), displ_line),
match.start(),
match.end(), line_dec),
self.total_matches)
else:
found = line.find(text)
while found > -1:
self.total_matches += 1
displ_line = self.truncate_result(line_dec,
found,
found + len(text))

self.sig_file_match.emit((osp.abspath(fname),
lineno + 1,
found, displ_line),
found,
found + len(text), line_dec),
self.total_matches)
for text, enc in self.texts:
found = line.find(text, found + 1)
Expand Down Expand Up @@ -493,23 +451,31 @@ def __ge__(self, x):


class FileMatchItem(QTreeWidgetItem):
def __init__(self, parent, filename):
def __init__(self, parent, filename, sorting):

self.sorting = sorting
self.filename = osp.basename(filename)

title = ('<b>{0}</b><br>'
'<small><em>{1}</em>'
'</small>'.format(osp.basename(filename),
osp.dirname(filename)))
title_format = to_text_string('<b>{0}</b><br>'
'<small><em>{1}</em>'
'</small>')
title = (title_format.format(osp.basename(filename),
osp.dirname(filename)))
QTreeWidgetItem.__init__(self, parent, [title], QTreeWidgetItem.Type)

self.setToolTip(0, filename)

def __lt__(self, x):
return self.filename < x.filename
if self.sorting['status'] == ON:
return self.filename < x.filename
else:
return False

def __ge__(self, x):
return self.filename >= x.filename
if self.sorting['status'] == ON:
return self.filename >= x.filename
else:
return False


class ItemDelegate(QStyledItemDelegate):
Expand Down Expand Up @@ -559,15 +525,17 @@ def __init__(self, parent):
self.total_matches = None
self.error_flag = None
self.completed = None
self.sorting = {}
self.data = None
self.files = None
self.set_title('')
self.set_sorting(OFF)
self.setSortingEnabled(False)
self.root_items = None
self.sortByColumn(0, Qt.AscendingOrder)
self.setSortingEnabled(True)
self.header().setSectionsClickable(True)
self.setItemDelegate(ItemDelegate(self))
self.setUniformRowHeights(False)
self.header().sectionClicked.connect(self.sort_section)

def activated(self, item):
"""Double-click event"""
Expand All @@ -576,28 +544,106 @@ def activated(self, item):
filename, lineno, colno = itemdata
self.parent().edit_goto.emit(filename, lineno, self.search_text)

def set_sorting(self, flag):
"""Enable result sorting after search is complete."""
self.sorting['status'] = flag
self.header().setSectionsClickable(flag == ON)

@Slot(int)
def sort_section(self, idx):
self.setSortingEnabled(True)

def clicked(self, item):
"""Click event"""
self.activated(item)

def clear_title(self, search_text):
self.clear()
self.setSortingEnabled(False)
self.num_files = 0
self.data = {}
self.files = {}
self.set_sorting(OFF)
self.search_text = search_text
title = "'%s' - " % search_text
text = _('String not found')
self.set_title(title + text)

def truncate_result(self, line, start, end):
ellipsis = '...'
max_line_length = 80
max_num_char_fragment = 40

html_escape_table = {
"&": "&amp;",
'"': "&quot;",
"'": "&apos;",
">": "&gt;",
"<": "&lt;",
}

def html_escape(text):
"""Produce entities within text."""
return "".join(html_escape_table.get(c, c) for c in text)

line = to_text_string(line)
left, match, right = line[:start], line[start:end], line[end:]

if len(line) > max_line_length:
offset = (len(line) - len(match)) // 2

left = left.split(' ')
num_left_words = len(left)

if num_left_words == 1:
left = left[0]
if len(left) > max_num_char_fragment:
left = ellipsis + left[-offset:]
left = [left]

right = right.split(' ')
num_right_words = len(right)

if num_right_words == 1:
right = right[0]
if len(right) > max_num_char_fragment:
right = right[:offset] + ellipsis
right = [right]

left = left[-4:]
right = right[:4]

if len(left) < num_left_words:
left = [ellipsis] + left

if len(right) < num_right_words:
right = right + [ellipsis]

left = ' '.join(left)
right = ' '.join(right)

if len(left) > max_num_char_fragment:
left = ellipsis + left[-30:]

if len(right) > max_num_char_fragment:
right = right[:30] + ellipsis

line_match_format = to_text_string('{0}<b>{1}</b>{2}')
left = html_escape(left)
right = html_escape(right)
match = html_escape(match)
trunc_line = line_match_format.format(left, match, right)
return trunc_line

@Slot(tuple, int)
def append_result(self, results, num_matches):
"""Real-time update of search results"""
filename, lineno, colno, line = results
filename, lineno, colno, match_end, line = results

if filename not in self.files:
item = FileMatchItem(self, filename)
self.files[filename] = item
file_item = FileMatchItem(self, filename, self.sorting)
file_item.setExpanded(True)
self.files[filename] = file_item
self.num_files += 1

search_text = self.search_text
Expand All @@ -615,8 +661,8 @@ def append_result(self, results, num_matches):
self.set_title(title + text)

file_item = self.files[filename]
line = self.truncate_result(line, colno, match_end)
item = LineMatchItem(file_item, lineno, colno, line)

self.data[id(item)] = (filename, lineno, colno)


Expand Down Expand Up @@ -692,7 +738,6 @@ def __init__(self, parent,

hlayout = QHBoxLayout()
hlayout.addWidget(self.result_browser)
# hlayout.addLayout(btn_layout)

layout = QVBoxLayout()
left, _x, right, bottom = layout.getContentsMargins()
Expand Down Expand Up @@ -755,6 +800,7 @@ def closing_widget(self):

def search_complete(self, completed):
"""Current search thread has finished"""
self.result_browser.set_sorting(ON)
self.find_options.ok_button.setEnabled(True)
self.find_options.stop_button.setEnabled(False)
self.status_bar.hide()
Expand Down