Skip to content

Commit

Permalink
Merge pull request #4822 from andfoy/find_in_files_folder
Browse files Browse the repository at this point in the history
PR: Extend Find in Files to select other search directories
  • Loading branch information
ccordoba12 authored Aug 16, 2017
2 parents f3bb1aa + 972a18d commit 9d488f4
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 56 deletions.
8 changes: 6 additions & 2 deletions spyder/plugins/findinfiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,13 @@ def __init__(self, parent=None):
in_python_path = self.get_option('in_python_path')
more_options = self.get_option('more_options')
case_sensitive = self.get_option('case_sensitive')
path_history = self.get_option('path_history', [])
FindInFilesWidget.__init__(self, parent,
search_text, search_text_regexp,
search_path, exclude, exclude_idx,
exclude_regexp, supported_encodings,
in_python_path, more_options,
case_sensitive)
case_sensitive, path_history)
SpyderPluginMixin.__init__(self, parent)

# Initialize plugin
Expand Down Expand Up @@ -160,11 +161,13 @@ def closing_plugin(self, cancelable=False):
if options is not None:
(search_text, text_re, search_path,
exclude, exclude_idx, exclude_re,
in_python_path, more_options, case_sensitive) = options
in_python_path, more_options, case_sensitive,
path_history) = options
hist_limit = 15
search_text = search_text[:hist_limit]
search_path = search_path[:hist_limit]
exclude = exclude[:hist_limit]
path_history = path_history[-hist_limit:]
self.set_option('search_text', search_text)
self.set_option('search_text_regexp', text_re)
self.set_option('search_path', search_path)
Expand All @@ -174,6 +177,7 @@ def closing_plugin(self, cancelable=False):
self.set_option('in_python_path', in_python_path)
self.set_option('more_options', more_options)
self.set_option('case_sensitive', case_sensitive)
self.set_option('path_history', path_history)
return True


Expand Down
183 changes: 130 additions & 53 deletions spyder/widgets/findinfiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
from qtpy.compat import getexistingdirectory
from qtpy.QtGui import QAbstractTextDocumentLayout, QTextDocument
from qtpy.QtCore import QMutex, QMutexLocker, Qt, QThread, Signal, Slot, QSize
from qtpy.QtWidgets import (QHBoxLayout, QLabel, QRadioButton, QSizePolicy,
from qtpy.QtWidgets import (QHBoxLayout, QLabel, QListWidget, QSizePolicy,
QTreeWidgetItem, QVBoxLayout, QWidget,
QStyledItemDelegate, QStyleOptionViewItem,
QApplication, QStyle)
QApplication, QStyle, QListWidgetItem)

# Local imports
from spyder.config.base import _
Expand All @@ -46,6 +46,22 @@
ON = 'on'
OFF = 'off'

CWD = 0
PROJECT = 1
FILE_PATH = 2
EXTERNAL_PATH = 4

MAX_PATH_LENGTH = 60
MAX_PATH_HISTORY = 15


def truncate_path(text):
ellipsis = '...'
part_len = (MAX_PATH_LENGTH - len(ellipsis)) / 2.0
left_text = text[:int(math.ceil(part_len))]
right_text = text[-int(math.floor(part_len)):]
return left_text + ellipsis + right_text


class SearchThread(QThread):
"""Find in files search thread"""
Expand Down Expand Up @@ -186,6 +202,23 @@ def get_results(self):
return self.results, self.pathlist, self.total_matches, self.error_flag


class ExternalPathItem(QListWidgetItem):
def __init__(self, parent, path):
self.path = path
QListWidgetItem.__init__(self, self.__repr__(), parent)

def __repr__(self):
if len(self.path) > MAX_PATH_LENGTH:
return truncate_path(self.path)
return self.path

def __str__(self):
return self.__repr__()

def __unicode__(self):
return self.__repr__()


class FindOptions(QWidget):
"""Find widget with options"""
REGEX_INVALID = "background-color:rgb(255, 175, 90);"
Expand All @@ -195,7 +228,7 @@ class FindOptions(QWidget):
def __init__(self, parent, search_text, search_text_regexp, search_path,
exclude, exclude_idx, exclude_regexp,
supported_encodings, in_python_path, more_options,
case_sensitive):
case_sensitive, external_path_history):
QWidget.__init__(self, parent)

if search_path is None:
Expand All @@ -204,13 +237,17 @@ def __init__(self, parent, search_text, search_text_regexp, search_path,
self.path = ''
self.project_path = None
self.file_path = None
self.external_path = None
self.external_path_history = external_path_history

if not isinstance(search_text, (list, tuple)):
search_text = [search_text]
if not isinstance(search_path, (list, tuple)):
search_path = [search_path]
if not isinstance(exclude, (list, tuple)):
exclude = [exclude]
if not isinstance(external_path_history, (list, tuple)):
external_path_history = [external_path_history]

self.supported_encodings = supported_encodings

Expand Down Expand Up @@ -272,28 +309,48 @@ def __init__(self, parent, search_text, search_text_regexp, search_path,
# Layout 3
hlayout3 = QHBoxLayout()

self.global_path_search = QRadioButton(_("Current working "
"directory"), self)
self.global_path_search.setChecked(True)
self.global_path_search.setToolTip(_("Search in all files and "
"directories present on the"
"current Spyder path"))

self.project_search = QRadioButton(_("Project"), self)
self.project_search.setToolTip(_("Search in all files and "
"directories present on the"
"current project path (If opened)"))

self.project_search.setEnabled(False)

self.file_search = QRadioButton(_("File"), self)
self.file_search.setToolTip(_("Search in current opened file"))

for wid in [self.global_path_search,
self.project_search, self.file_search]:
hlayout3.addWidget(wid)

hlayout3.addStretch(1)
search_on_label = QLabel(_("Search in:"))
self.path_selection_combo = PatternComboBox(self, exclude,
_('Search directory'))
self.path_selection_combo.setEditable(False)
self.path_selection_contents = QListWidget(self.path_selection_combo)
self.path_selection_contents.hide()
self.path_selection_combo.setModel(
self.path_selection_contents.model())

self.path_selection_contents.addItem(_("Current working directory"))
item = self.path_selection_contents.item(0)
item.setToolTip(_("Search in all files and "
"directories present on the"
"current Spyder path"))

self.path_selection_contents.addItem(_("Project"))
item = self.path_selection_contents.item(1)
item.setToolTip(_("Search in all files and "
"directories present on the"
"current project path "
"(If opened)"))
item.setFlags(item.flags() & ~Qt.ItemIsEnabled)

self.path_selection_contents.addItem(_("File"))
item = self.path_selection_contents.item(2)
item.setToolTip(_("Search in current opened file"))

self.path_selection_contents.addItem(_("Select other directory"))
item = self.path_selection_contents.item(3)
item.setToolTip(_("Search in other folder present on the file system"))

self.path_selection_combo.insertSeparator(3)
self.path_selection_combo.insertSeparator(5)
for path in external_path_history:
item = ExternalPathItem(None, path)
self.path_selection_contents.addItem(item)

self.path_selection_combo.currentIndexChanged.connect(
self.path_selection_changed)

hlayout3.addWidget(search_on_label)
hlayout3.addWidget(self.path_selection_combo)

self.search_text.valid.connect(lambda valid: self.find.emit())
self.exclude_pattern.valid.connect(lambda valid: self.find.emit())
Expand Down Expand Up @@ -324,6 +381,28 @@ def toggle_more_options(self, state):
self.more_options.setIcon(icon)
self.more_options.setToolTip(tip)

@Slot()
def path_selection_changed(self):
idx = self.path_selection_combo.currentIndex()
if idx == EXTERNAL_PATH:
external_path = self.select_directory()
if len(external_path) > 0:
item = ExternalPathItem(None, external_path)
self.path_selection_contents.addItem(item)

total_items = (self.path_selection_combo.count() -
MAX_PATH_HISTORY)
for i in range(6, total_items):
self.path_selection_contents.takeItem(i)

self.path_selection_combo.setCurrentIndex(
self.path_selection_combo.count() - 1)
else:
self.path_selection_combo.setCurrentIndex(CWD)
elif idx > EXTERNAL_PATH:
item = self.path_selection_contents.item(idx)
self.external_path = item.path

def update_combos(self):
self.search_text.lineEdit().returnPressed.emit()
self.exclude_pattern.lineEdit().returnPressed.emit()
Expand Down Expand Up @@ -360,16 +439,17 @@ def get_options(self, all=False):
if not case_sensitive:
texts = [(text[0].lower(), text[1]) for text in texts]

global_path_search = self.global_path_search.isChecked()
project_search = self.project_search.isChecked()
file_search = self.file_search.isChecked()

if global_path_search:
file_search = False
selection_idx = self.path_selection_combo.currentIndex()
if selection_idx == CWD:
path = self.path
elif project_search:
elif selection_idx == PROJECT:
path = self.project_path
else:
elif selection_idx == FILE_PATH:
path = self.file_path
file_search = True
else:
path = self.external_path

# Finding text occurrences
if not exclude_re:
Expand All @@ -394,34 +474,38 @@ def get_options(self, all=False):
for index in range(self.search_text.count())]
exclude = [to_text_string(self.exclude_pattern.itemText(index))
for index in range(self.exclude_pattern.count())]
path_history = [to_text_string(
self.path_selection_contents.item(index))
for index in range(
6, self.path_selection_combo.count())]
exclude_idx = self.exclude_pattern.currentIndex()
more_options = self.more_options.isChecked()
return (search_text, text_re, [],
exclude, exclude_idx, exclude_re,
python_path, more_options, case_sensitive)
python_path, more_options, case_sensitive, path_history)
else:
return (path, file_search, exclude, texts, text_re, case_sensitive)

@Slot()
def select_directory(self):
"""Select directory"""
self.parent().redirect_stdio.emit(False)
directory = getexistingdirectory(self, _("Select directory"),
self.dir_combo.currentText())
self.path)
if directory:
self.set_directory(directory)
self.parent().redirect_stdio.emit(True)
directory = to_text_string(osp.abspath(to_text_string(directory)))
return directory

def set_directory(self, directory):
self.path = to_text_string(osp.abspath(to_text_string(directory)))

def set_project_path(self, path):
self.project_path = to_text_string(osp.abspath(to_text_string(path)))
self.project_search.setEnabled(True)
item = self.path_selection_contents.item(PROJECT)
item.setFlags(item.flags() | Qt.ItemIsEnabled)

def disable_project_search(self):
self.project_search.setEnabled(False)
self.project_search.setChecked(False)
item = self.path_selection_contents.item(PROJECT)
item.setFlags(item.flags() & ~Qt.ItemIsEnabled)
self.project_path = None

def set_file_path(self, path):
Expand Down Expand Up @@ -687,7 +771,6 @@ def append_result(self, results, num_matches):

class FileProgressBar(QWidget):
"""Simple progress spinner with a label"""
MAX_LABEL_LENGTH = 60

def __init__(self, parent):
QWidget.__init__(self, parent)
Expand All @@ -702,16 +785,9 @@ def __init__(self, parent):
layout.addWidget(self.status_text)
self.setLayout(layout)

def __truncate(self, text):
ellipsis = '...'
part_len = (self.MAX_LABEL_LENGTH - len(ellipsis)) / 2.0
left_text = text[:int(math.ceil(part_len))]
right_text = text[-int(math.floor(part_len)):]
return left_text + ellipsis + right_text

@Slot(str)
def set_label_path(self, path, folder=False):
text = self.__truncate(path)
text = truncate_path(path)
if not folder:
status_str = _(u' Scanning: {0}').format(text)
else:
Expand All @@ -735,7 +811,7 @@ def __init__(self, parent,
exclude_regexp=True,
supported_encodings=("utf-8", "iso-8859-1", "cp1252"),
in_python_path=False, more_options=False,
case_sensitive=True):
case_sensitive=True, external_path_history=[]):
QWidget.__init__(self, parent)

self.setWindowTitle(_('Find in files'))
Expand All @@ -750,7 +826,8 @@ def __init__(self, parent,
search_path,
exclude, exclude_idx, exclude_regexp,
supported_encodings, in_python_path,
more_options, case_sensitive)
more_options, case_sensitive,
external_path_history)
self.find_options.find.connect(self.find)
self.find_options.stop.connect(self.stop_and_reset_thread)

Expand Down Expand Up @@ -778,8 +855,8 @@ def find(self):
return
self.stop_and_reset_thread(ignore_results=True)
self.search_thread = SearchThread(self)
self.search_thread.get_pythonpath_callback = \
self.get_pythonpath_callback
self.search_thread.get_pythonpath_callback = (
self.get_pythonpath_callback)
self.search_thread.sig_finished.connect(self.search_complete)
self.search_thread.sig_current_file.connect(
lambda x: self.status_bar.set_label_path(x, folder=False)
Expand Down
1 change: 0 additions & 1 deletion spyder/widgets/tests/test_findinfiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ def test_find_in_files_search(qtbot):
blocker = qtbot.waitSignal(find_in_files.sig_finished)
blocker.wait()
matches = process_search_results(find_in_files.result_browser.data)
print(matches)
assert expected_results() == matches


Expand Down

0 comments on commit 9d488f4

Please sign in to comment.