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: Add an option to show/hide line numbers to History #5363

Merged
merged 3 commits into from
Oct 11, 2017
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions spyder/config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@
'max_entries': 100,
'wrap': True,
'go_to_eof': True,
'line_numbers': False,
}),
('help',
{
Expand Down
30 changes: 26 additions & 4 deletions spyder/plugins/history.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ def setup_page(self):

sourcecode_group = QGroupBox(_("Source code"))
wrap_mode_box = self.create_checkbox(_("Wrap lines"), 'wrap')
linenumbers_mode_box = self.create_checkbox(_("Show line numbers"),
'line_numbers')
go_to_eof_box = self.create_checkbox(
_("Scroll automatically to last entry"), 'go_to_eof')

Expand All @@ -52,6 +54,7 @@ def setup_page(self):

sourcecode_layout = QVBoxLayout()
sourcecode_layout.addWidget(wrap_mode_box)
sourcecode_layout.addWidget(linenumbers_mode_box)
sourcecode_layout.addWidget(go_to_eof_box)
sourcecode_group.setLayout(sourcecode_layout)

Expand All @@ -77,6 +80,7 @@ def __init__(self, parent):
self.menu_actions = None
self.dockviewer = None
self.wrap_action = None
self.linenumbers_action = None

self.editors = []
self.filenames = []
Expand Down Expand Up @@ -147,14 +151,18 @@ def refresh_plugin(self):

def get_plugin_actions(self):
"""Return a list of actions related to plugin"""
history_action = create_action(self, _("History..."),
self.history_action = create_action(self, _("History..."),
None, ima.icon('history'),
_("Set history maximum entries"),
triggered=self.change_history_depth)
self.wrap_action = create_action(self, _("Wrap lines"),
toggled=self.toggle_wrap_mode)
self.wrap_action.setChecked( self.get_option('wrap') )
self.menu_actions = [history_action, self.wrap_action]
self.linenumbers_action = create_action(
self, _("Show line numbers"), toggled=self.toggle_line_numbers)
self.linenumbers_action.setChecked(self.get_option('line_numbers'))
self.menu_actions = [self.history_action, self.wrap_action,
self.linenumbers_action]
return self.menu_actions

def on_first_registration(self):
Expand Down Expand Up @@ -184,6 +192,8 @@ def apply_plugin_settings(self, options):
wrap_n = 'wrap'
wrap_o = self.get_option(wrap_n)
self.wrap_action.setChecked(wrap_o)
linenb_n = 'line_numbers'
linenb_o = self.get_option(linenb_n)
for editor in self.editors:
if font_n in options:
scs = color_scheme_o if color_scheme_n in options else None
Expand All @@ -192,7 +202,9 @@ def apply_plugin_settings(self, options):
editor.set_color_scheme(color_scheme_o)
if wrap_n in options:
editor.toggle_wrap_mode(wrap_o)

if linenb_n in options:
editor.toggle_line_numbers(linenumbers=linenb_o, markers=False)

#------ Private API --------------------------------------------------------
def move_tab(self, index_from, index_to):
"""
Expand All @@ -218,7 +230,8 @@ def add_history(self, filename):
language = 'py'
else:
language = 'bat'
editor.setup_editor(linenumbers=False, language=language,
editor.setup_editor(linenumbers=self.get_option('line_numbers'),
language=language,
scrollflagarea=False)
editor.focus_changed.connect(lambda: self.focus_changed.emit())
editor.setReadOnly(True)
Expand Down Expand Up @@ -270,3 +283,12 @@ def toggle_wrap_mode(self, checked):
for editor in self.editors:
editor.toggle_wrap_mode(checked)
self.set_option('wrap', checked)

@Slot(bool)
def toggle_line_numbers(self, checked):
"""Toggle line numbers."""
if self.tabwidget is None:
return
for editor in self.editors:
editor.toggle_line_numbers(linenumbers=checked, markers=False)
self.set_option('line_numbers', checked)
266 changes: 266 additions & 0 deletions spyder/plugins/tests/test_history.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
# -*- coding: utf-8 -*-
#
# Copyright © Spyder Project Contributors
# Licensed under the terms of the MIT License
#

import pytest

from qtpy.QtGui import QTextOption

from spyder.plugins import history

#==============================================================================
# Utillity Functions
#==============================================================================
options = {'wrap': False,
'line_numbers': False,
'go_to_eof': True,
'max_entries': 100}


def get_option(self, option):
global options
return options[option]


def set_option(self, option, value):
global options
options[option] = value

#==============================================================================
# Qt Test Fixtures
#==============================================================================
@pytest.fixture
def historylog(qtbot, monkeypatch):
"""Return a fixture for base history log, which is a plugin widget."""
monkeypatch.setattr(history.HistoryLog,
'register_widget_shortcuts',
lambda *args: None)
historylog = history.HistoryLog(None)
qtbot.addWidget(historylog)
historylog.show()
yield historylog
historylog.closing_plugin()
historylog.close()


@pytest.fixture
def historylog_with_tab(historylog, mocker, monkeypatch):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a docstring to describe what this fixture is about.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

"""Return a fixture for a history log with one tab.

The base history log is a plugin widget. Within the plugin widget,
the method add_history creates a tab containing a code editor
for each history file. This fixture creates a history log with
one tab containing no text.
"""
hl = historylog
# Mock read so file doesn't have to exist.
mocker.patch.object(history.encoding, 'read')
history.encoding.read.return_value = ('', '')

# Monkeypatch current options.
monkeypatch.setattr(history.HistoryLog, 'get_option', get_option)
monkeypatch.setattr(history.HistoryLog, 'set_option', set_option)

# Create tab for page.
hl.set_option('wrap', False)
hl.set_option('line_numbers', False)
hl.set_option('max_entries', 100)
hl.set_option('go_to_eof', True)
hl.add_history('test_history.py')
return hl

#==============================================================================
# Tests
#==============================================================================

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this blank line

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

def test_init(historylog):
Copy link
Member

@ccordoba12 ccordoba12 Oct 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a short docstring to all tests to describe what each one is testing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. My tests matched to the methods, so I wasn't quite sure what to write. If it's too redundant, I can change it.

"""Test HistoryLog.__init__.

Test that the initialization created the expected instance variables
and widgets for a new HistoryLog instance.
"""
hl = historylog
assert hl.editors == []
assert hl.filenames == []
assert hl.plugin_actions == hl.menu_actions
assert hl.tabwidget.menu.actions() == hl.menu_actions
assert hl.tabwidget.cornerWidget().menu().actions() == hl.menu_actions


def test_add_history(historylog, mocker, monkeypatch):
"""Test the add_history method.

Test adding a history file to the history log widget and the
code editor properties that are enabled/disabled.
"""
hl = historylog
hle = hl.editors

# Mock read so file doesn't have to exist.
mocker.patch.object(history.encoding, 'read')

# Monkeypatch current options.
monkeypatch.setattr(history.HistoryLog, 'get_option', get_option)
monkeypatch.setattr(history.HistoryLog, 'set_option', set_option)
# No editors yet.
assert len(hl.editors) == 0

# Add one file.
tab1 = 'test_history.py'
text1 = 'a = 5\nb= 10\na + b\n'
hl.set_option('line_numbers', False)
hl.set_option('wrap', False)
history.encoding.read.return_value = (text1, '')
hl.add_history(tab1)
# Check tab and editor were created correctly.
assert len(hle) == 1
assert hl.filenames == [tab1]
assert hl.tabwidget.currentIndex() == 0
assert not hle[0].linenumberarea.isVisible()
assert hle[0].wordWrapMode() == QTextOption.NoWrap
assert hl.tabwidget.tabText(0) == tab1
assert hl.tabwidget.tabToolTip(0) == tab1

hl.set_option('line_numbers', True)
hl.set_option('wrap', True)
# Try to add same file -- does not process filename again, so
# linenumbers and wrap doesn't change.
hl.add_history(tab1)
assert hl.tabwidget.currentIndex() == 0
assert not hl.editors[0].linenumberarea.isVisible()

# Add another file.
tab2 = 'history2.js'
text2 = 'random text\nspam line\n\n\n\n'
history.encoding.read.return_value = (text2, '')
hl.add_history(tab2)
# Check second tab and editor were created correctly.
assert len(hle) == 2
assert hl.filenames == [tab1, tab2]
assert hl.tabwidget.currentIndex() == 1
assert hle[1].linenumberarea.isVisible()
assert hle[1].wordWrapMode() == QTextOption.WrapAtWordBoundaryOrAnywhere
assert hl.tabwidget.tabText(1) == tab2
assert hl.tabwidget.tabToolTip(1) == tab2

assert hl.filenames == [tab1, tab2]

# Check differences between tabs based on setup.
assert hle[0].supported_language
assert hle[0].is_python()
assert hle[0].isReadOnly()
assert not hle[0].isVisible()
assert hle[0].toPlainText() == text1

assert not hle[1].supported_language
assert not hle[1].is_python()
assert hle[1].isReadOnly()
assert hle[1].isVisible()
assert hle[1].toPlainText() == text2


def test_append_to_history(historylog_with_tab, mocker):
"""Test the append_to_history method.

Test adding text to a history file. Also test the go_to_eof config
option for positioning the cursor.
"""
hl = historylog_with_tab

# Toggle to move to the end of the file after appending.
hl.set_option('go_to_eof', True)
# Force cursor to the beginning of the file.
hl.editors[0].set_cursor_position('sof')
hl.append_to_history('test_history.py', 'import re\n')
assert hl.editors[0].toPlainText() == 'import re\n'
assert hl.tabwidget.currentIndex() == 0
# Cursor moved to end.
assert hl.editors[0].is_cursor_at_end()
assert not hl.editors[0].linenumberarea.isVisible()

# Toggle to not move cursor after appending.
hl.set_option('go_to_eof', False)
# Force cursor to the beginning of the file.
hl.editors[0].set_cursor_position('sof')
hl.append_to_history('test_history.py', 'a = r"[a-z]"\n')
assert hl.editors[0].toPlainText() == 'import re\na = r"[a-z]"\n'
# Cursor not at end.
assert not hl.editors[0].is_cursor_at_end()


def test_change_history_depth(historylog_with_tab, mocker):
"""Test the change_history_depth method.

Modify the 'Maximum history entries' values to test the config action.
"""
hl = historylog_with_tab
action = hl.history_action
# Mock dialog.
mocker.patch.object(history.QInputDialog, 'getInt')

# Starts with default.
assert hl.get_option('max_entries') == 100

# Invalid data.
history.QInputDialog.getInt.return_value = (10, False)
action.trigger()
assert hl.get_option('max_entries') == 100 # No change.

# Valid data.
history.QInputDialog.getInt.return_value = (475, True)
action.trigger()
assert hl.get_option('max_entries') == 475


def test_toggle_wrap_mode(historylog_with_tab):
"""Test the toggle_wrap_mode method.

Toggle the 'Wrap lines' config action.
"""
hl = historylog_with_tab
action = hl.wrap_action
action.setChecked(False)

# Starts with wrap mode off.
assert hl.editors[0].wordWrapMode() == QTextOption.NoWrap
assert not hl.get_option('wrap')

# Toggles wrap mode on.
action.setChecked(True)
assert hl.editors[0].wordWrapMode() == QTextOption.WrapAtWordBoundaryOrAnywhere
assert hl.get_option('wrap')

# Toggles wrap mode off.
action.setChecked(False)
assert hl.editors[0].wordWrapMode() == QTextOption.NoWrap
assert not hl.get_option('wrap')


def test_toggle_line_numbers(historylog_with_tab):
"""Test toggle_line_numbers method.

Toggle the 'Show line numbers' config action.
"""
hl = historylog_with_tab
action = hl.linenumbers_action
action.setChecked(False)

# Starts without line numbers.
assert not hl.editors[0].linenumberarea.isVisible()
assert not hl.get_option('line_numbers')

# Toggles line numbers on.
action.setChecked(True)
assert hl.editors[0].linenumberarea.isVisible()
assert hl.get_option('line_numbers')

# Toggles line numbers off.
action.setChecked(False)
assert not hl.editors[0].linenumberarea.isVisible()
assert not hl.get_option('line_numbers')


if __name__ == "__main__":
pytest.main()
6 changes: 5 additions & 1 deletion spyder/widgets/sourcecode/codeeditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,10 @@ def toggle_wrap_mode(self, enable):
"""Enable/disable wrap mode"""
self.set_wrap_mode('word' if enable else None)

def toggle_line_numbers(self, linenumbers=True, markers=False):
"""Enable/disable line numbers."""
self.linenumberarea.setup_margins(linenumbers, markers)

@property
def panels(self):
"""
Expand Down Expand Up @@ -671,7 +675,7 @@ def setup_editor(self, linenumbers=True, language=None, markers=False,
# Line number area
if cloned_from:
self.setFont(font) # this is required for line numbers area
self.linenumberarea.setup_margins(linenumbers, markers)
self.toggle_line_numbers(linenumbers, markers)

# Lexer
self.set_language(language, filename)
Expand Down