Merge branch 'EliteTK-history-clear'

This commit is contained in:
Florian Bruhin 2016-06-08 11:40:00 +02:00
commit a5a4c17b1f
8 changed files with 83 additions and 76 deletions

View File

@ -34,6 +34,7 @@ Added
pressed. This can be turned off using `:set ui keyhint-blacklist *`. pressed. This can be turned off using `:set ui keyhint-blacklist *`.
- New `hints -> auto-follow-timeout` setting to ignore keypresses after - New `hints -> auto-follow-timeout` setting to ignore keypresses after
following a hint when filtering in number mode. following a hint when filtering in number mode.
- New `:history-clear` command to clear the entire history
Changed Changed
~~~~~~~ ~~~~~~~

View File

@ -188,6 +188,7 @@ Contributors, sorted by the number of commits in descending order:
* sbinix * sbinix
* neeasade * neeasade
* jnphilipp * jnphilipp
* Tomasz Kramkowski
* Tobias Patzl * Tobias Patzl
* Stefan Tatschner * Stefan Tatschner
* Peter Michely * Peter Michely
@ -211,7 +212,6 @@ Contributors, sorted by the number of commits in descending order:
* evan * evan
* dylan araps * dylan araps
* Xitian9 * Xitian9
* Tomasz Kramkowski
* Tomas Orsava * Tomas Orsava
* Tobias Werth * Tobias Werth
* Tim Harder * Tim Harder

View File

@ -26,6 +26,7 @@
|<<fullscreen,fullscreen>>|Toggle fullscreen mode. |<<fullscreen,fullscreen>>|Toggle fullscreen mode.
|<<help,help>>|Show help about a command or setting. |<<help,help>>|Show help about a command or setting.
|<<hint,hint>>|Start hinting. |<<hint,hint>>|Start hinting.
|<<history-clear,history-clear>>|Clear all history entries.
|<<home,home>>|Open main startpage in current tab. |<<home,home>>|Open main startpage in current tab.
|<<inspector,inspector>>|Toggle the web inspector. |<<inspector,inspector>>|Toggle the web inspector.
|<<jseval,jseval>>|Evaluate a JavaScript string. |<<jseval,jseval>>|Evaluate a JavaScript string.
@ -356,6 +357,10 @@ Start hinting.
==== note ==== note
* This command does not split arguments after the last argument and handles quotes literally. * This command does not split arguments after the last argument and handles quotes literally.
[[history-clear]]
=== history-clear
Clear all history entries.
[[home]] [[home]]
=== home === home
Open main startpage in current tab. Open main startpage in current tab.

View File

@ -25,6 +25,7 @@ import collections
from PyQt5.QtCore import pyqtSignal, QUrl from PyQt5.QtCore import pyqtSignal, QUrl
from PyQt5.QtWebKit import QWebHistoryInterface from PyQt5.QtWebKit import QWebHistoryInterface
from qutebrowser.commands import cmdutils
from qutebrowser.utils import utils, objreg, standarddir, log from qutebrowser.utils import utils, objreg, standarddir, log
from qutebrowser.config import config from qutebrowser.config import config
from qutebrowser.misc import lineparser from qutebrowser.misc import lineparser
@ -72,10 +73,12 @@ class WebHistory(QWebHistoryInterface):
arg: The new HistoryEntry. arg: The new HistoryEntry.
item_added: Emitted after a new HistoryEntry is added. item_added: Emitted after a new HistoryEntry is added.
arg: The new HistoryEntry. arg: The new HistoryEntry.
cleared: Emitted after the history is cleared.
""" """
add_completion_item = pyqtSignal(HistoryEntry) add_completion_item = pyqtSignal(HistoryEntry)
item_added = pyqtSignal(HistoryEntry) item_added = pyqtSignal(HistoryEntry)
cleared = pyqtSignal()
async_read_done = pyqtSignal() async_read_done = pyqtSignal()
def __init__(self, parent=None): def __init__(self, parent=None):
@ -169,6 +172,16 @@ class WebHistory(QWebHistoryInterface):
self._lineparser.save() self._lineparser.save()
self._saved_count = len(self._new_history) self._saved_count = len(self._new_history)
@cmdutils.register(name='history-clear', instance='web-history')
def clear(self):
"""Clear all history entries."""
self._lineparser.clear()
self._history_dict.clear()
self._temp_history.clear()
self._new_history.clear()
self._saved_count = 0
self.cleared.emit()
def addHistoryEntry(self, url_string): def addHistoryEntry(self, url_string):
"""Called by WebKit when an URL should be added to the history. """Called by WebKit when an URL should be added to the history.

View File

@ -76,6 +76,7 @@ class UrlCompletionModel(base.BaseCompletionModel):
for entry in history: for entry in history:
self._add_history_entry(entry) self._add_history_entry(entry)
self._history.add_completion_item.connect(self.on_history_item_added) self._history.add_completion_item.connect(self.on_history_item_added)
self._history.cleared.connect(self.on_history_cleared)
objreg.get('config').changed.connect(self.reformat_timestamps) objreg.get('config').changed.connect(self.reformat_timestamps)
@ -130,6 +131,10 @@ class UrlCompletionModel(base.BaseCompletionModel):
else: else:
self._add_history_entry(entry) self._add_history_entry(entry)
@pyqtSlot()
def on_history_cleared(self):
self._history_cat.removeRows(0, self._history_cat.rowCount())
def _remove_item(self, data, category, column): def _remove_item(self, data, category, column):
"""Helper function for on_quickmark_removed and on_bookmark_removed. """Helper function for on_quickmark_removed and on_bookmark_removed.

View File

@ -114,6 +114,8 @@ class BaseLineParser(QObject):
fp: A file object to write the data to. fp: A file object to write the data to.
data: The data to write. data: The data to write.
""" """
if not data:
return
if self._binary: if self._binary:
fp.write(b'\n'.join(data)) fp.write(b'\n'.join(data))
fp.write(b'\n') fp.write(b'\n')
@ -125,6 +127,10 @@ class BaseLineParser(QObject):
"""Save the history to disk.""" """Save the history to disk."""
raise NotImplementedError raise NotImplementedError
def clear(self):
"""Clear the contents of the file."""
raise NotImplementedError
class AppendLineParser(BaseLineParser): class AppendLineParser(BaseLineParser):
@ -183,6 +189,15 @@ class AppendLineParser(BaseLineParser):
self.new_data = [] self.new_data = []
self._after_save() self._after_save()
def clear(self):
do_save = self._prepare_save()
if not do_save:
return
with self._open('w'):
pass
self.new_data = []
self._after_save()
class LineParser(BaseLineParser): class LineParser(BaseLineParser):
@ -237,6 +252,10 @@ class LineParser(BaseLineParser):
self._opened = False self._opened = False
self._after_save() self._after_save()
def clear(self):
self.data = []
self.save()
class LimitLineParser(LineParser): class LimitLineParser(LineParser):

View File

@ -57,6 +57,9 @@ class LineparserSaveStub(lineparser.BaseLineParser):
def save(self): def save(self):
self.saved = self.data self.saved = self.data
def clear(self):
pass
def __iter__(self): def __iter__(self):
return iter(self.data) return iter(self.data)

View File

@ -19,80 +19,11 @@
"""Tests for qutebrowser.misc.lineparser.""" """Tests for qutebrowser.misc.lineparser."""
import io
import os
import pytest import pytest
from qutebrowser.misc import lineparser as lineparsermod from qutebrowser.misc import lineparser as lineparsermod
class LineParserMixin:
"""A wrapper over lineparser.BaseLineParser to make it testable."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._data = None
self._test_save_prepared = False
def _open(self, mode):
"""Override _open to use StringIO/BytesIO instead of a real file."""
if mode not in 'rwa':
raise ValueError("Unknown mode {!r}!".format(mode))
if self._test_save_prepared:
self._test_save_prepared = False
elif mode != 'r':
raise ValueError("Doing unprepared save!")
if mode in 'ar' and self._data is not None:
prev_val = self._data
else:
prev_val = None
if self._binary:
fobj = io.BytesIO(prev_val)
else:
fobj = io.StringIO(prev_val)
if mode == 'a':
fobj.seek(0, os.SEEK_END)
return fobj
def _write(self, fp, data):
"""Extend _write to get the data after writing it."""
super()._write(fp, data)
self._data = fp.getvalue()
def _prepare_save(self):
"""Keep track if _prepare_save has been called."""
self._test_save_prepared = True
return True
class AppendLineParserTestable(LineParserMixin,
lineparsermod.AppendLineParser):
"""Wrapper over AppendLineParser to make it testable."""
pass
class LineParserTestable(LineParserMixin, lineparsermod.LineParser):
"""Wrapper over LineParser to make it testable."""
pass
class LimitLineParserTestable(LineParserMixin,
lineparsermod.LimitLineParser):
"""Wrapper over LimitLineParser to make it testable."""
pass
class TestBaseLineParser: class TestBaseLineParser:
"""Tests for BaseLineParser.""" """Tests for BaseLineParser."""
@ -122,6 +53,24 @@ class TestBaseLineParser:
os_mock.makedirs.assert_called_with(self.CONFDIR, 0o755) os_mock.makedirs.assert_called_with(self.CONFDIR, 0o755)
class TestLineParser:
@pytest.fixture
def lineparser(self, tmpdir):
"""Fixture to get a LineParser for tests."""
lp = lineparsermod.LineParser(str(tmpdir), 'file')
lp.save()
return lp
def test_clear(self, tmpdir, lineparser):
lineparser.data = ['one', 'two']
lineparser.save()
assert (tmpdir / 'file').read() == 'one\ntwo\n'
lineparser.clear()
assert not lineparser.data
assert (tmpdir / 'file').read() == ''
class TestAppendLineParser: class TestAppendLineParser:
"""Tests for AppendLineParser.""" """Tests for AppendLineParser."""
@ -129,9 +78,9 @@ class TestAppendLineParser:
BASE_DATA = ['old data 1', 'old data 2'] BASE_DATA = ['old data 1', 'old data 2']
@pytest.fixture @pytest.fixture
def lineparser(self): def lineparser(self, tmpdir):
"""Fixture to get an AppendLineParser for tests.""" """Fixture to get an AppendLineParser for tests."""
lp = AppendLineParserTestable('this really', 'does not matter') lp = lineparsermod.AppendLineParser(str(tmpdir), 'file')
lp.new_data = self.BASE_DATA lp.new_data = self.BASE_DATA
lp.save() lp.save()
return lp return lp
@ -140,12 +89,23 @@ class TestAppendLineParser:
"""Get the expected data with newlines.""" """Get the expected data with newlines."""
return '\n'.join(self.BASE_DATA + new_data) + '\n' return '\n'.join(self.BASE_DATA + new_data) + '\n'
def test_save(self, lineparser): def test_save(self, tmpdir, lineparser):
"""Test save().""" """Test save()."""
new_data = ['new data 1', 'new data 2'] new_data = ['new data 1', 'new data 2']
lineparser.new_data = new_data lineparser.new_data = new_data
lineparser.save() lineparser.save()
assert lineparser._data == self._get_expected(new_data) assert (tmpdir / 'file').read() == self._get_expected(new_data)
def test_clear(self, tmpdir, lineparser):
lineparser.new_data = ['one', 'two']
lineparser.save()
assert (tmpdir / 'file').read() == "old data 1\nold data 2\none\ntwo\n"
lineparser.new_data = ['one', 'two']
lineparser.clear()
lineparser.save()
assert not lineparser.new_data
assert (tmpdir / 'file').read() == ""
def test_iter_without_open(self, lineparser): def test_iter_without_open(self, lineparser):
"""Test __iter__ without having called open().""" """Test __iter__ without having called open()."""
@ -170,9 +130,10 @@ class TestAppendLineParser:
with linep.open(): with linep.open():
assert list(linep) == new_data assert list(linep) == new_data
def test_get_recent_none(self): def test_get_recent_none(self, tmpdir):
"""Test get_recent with no data.""" """Test get_recent with no data."""
linep = AppendLineParserTestable('this really', 'does not matter') (tmpdir / 'file2').ensure()
linep = lineparsermod.AppendLineParser(str(tmpdir), 'file2')
assert linep.get_recent() == [] assert linep.get_recent() == []
def test_get_recent_little(self, lineparser): def test_get_recent_little(self, lineparser):