Philipp Hörist pushed to branch master at gajim / gajim
Commits: 4a30ee3f by lovetox at 2022-08-11T20:26:47+02:00 refactor: Regex: Remove unused code
- Add licence header - Fix tests
- - - - - 537bce0d by lovetox at 2022-08-11T21:41:23+02:00 refactor: ScrolledView: Improve emitting signals
- Block all signals instead of only request-history - Improve reset logic to prevent clearing the view twice
- - - - -
4 changed files:
- gajim/common/regex.py - gajim/gtk/control.py - gajim/gtk/conversation/scrolled.py - test/no_gui/test_regex.py
Changes:
===================================== gajim/common/regex.py ===================================== @@ -1,88 +1,19 @@ -import re - - -def _get_link_pattern(): - # regexp meta characters are: . ^ $ * + ? { } [ ] \ | ( ) - # one escapes the metachars with \ - # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v' - # \s matches any whitespace character - # \w any alphanumeric character - # \W any non-alphanumeric character - # \b means word boundary. This is a zero-width assertion that - # matches only at the beginning or end of a word. - # ^ matches at the beginning of lines - # - # * means 0 or more times - # + means 1 or more times - # ? means 0 or 1 time - # | means or - # [^*] anything but '*' (inside [] you don't have to escape metachars) - # [^\s*] anything but whitespaces and '*' - # (?<!\S) is a one char lookbehind assertion and asks for any leading - # whitespace - # and matches beginning of lines so we have correct formatting detection - # even if the text is just '*foo*' - # (?!\S) is the same thing but it's a lookahead assertion - # \S*[^\s\W] --> in the matching string don't match ? or ) etc.. if at - # the end - # so http://be) will match http://be and http://be)be) will match - # http://be)be - - legacy_prefixes = r'((?<=()(www|ftp).([A-Za-z0-9.-_~:/?#[]@!$'\ - r"&'()*+,;=]|%[A-Fa-f0-9]{2})+(?=)))"\ - r"|((www|ftp).([A-Za-z0-9.-_~:/?#[]@!$&'()*+,;=]"\ - r'|%[A-Fa-f0-9]{2})+'\ - r".([A-Za-z0-9.-_~:/?#[]@!$&'()*+,;=]|%[A-Fa-f0-9]{2})+)" - # NOTE: it's ok to catch www.gr such stuff exist! - - # FIXME: recognize xmpp: and treat it specially - links = r'((?<=()[A-Za-z][A-Za-z0-9+.-]*:'\ - r"([\w.-_~:/?#[]@!$&'()*+,;=]|%[A-Fa-f0-9]{2})+"\ - r'(?=)))|(\w[\w+.-]*:([^<>\s]|%[A-Fa-f0-9]{2})+)' - - # 2nd one: at_least_one_char@at_least_one_char.at_least_one_char - mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+.\S*[^\s\W]' - - link_pattern = links + '|' + mail + '|' + legacy_prefixes - return link_pattern - - -def _get_basic_pattern(): - basic_pattern = _get_link_pattern() - # detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*) - # doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold* - formatting = r'|(?<!\w)' r'*[^\s*]' r'([^*]*[^\s*])?' r'*(?!\w)|'\ - r'(?<!\S)' r'~[^\s~]' r'([^~]*[^\s~])?' r'~(?!\S)|'\ - r'(?<!\w)' r'_[^\s_]' r'([^_]*[^\s_])?' r'_(?!\w)' - return basic_pattern + formatting +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see http://www.gnu.org/licenses/.
+import re
-def _get_emot_and_basic_pattern(use_ascii_formatting: bool = True) -> str: - from gajim.gui.emoji_data import emoji_data - # because emoticons match later (in the string) they need to be after - # basic matches that may occur earlier - emoticons = emoji_data.get_regex() - - if use_ascii_formatting: - pattern = _get_basic_pattern() - else: - pattern = _get_link_pattern() - - return '%s|%s' % (pattern, emoticons) - - -LINK_REGEX = re.compile(_get_link_pattern(), re.I | re.U) - -# link pattern + ASCII formatting -BASIC_REGEX = re.compile(_get_basic_pattern(), re.IGNORECASE) - -# emoticons + link pattern -EMOT_AND_LINK_REGEX = re.compile(_get_emot_and_basic_pattern(False), - re.IGNORECASE) - -# emoticons + link pattern + ASCII formatting -EMOT_AND_BASIC_REGEX = re.compile(_get_emot_and_basic_pattern(True), - re.IGNORECASE)
INVALID_XML_CHARS_REGEX = re.compile( '[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x1f]|[\ud800-\udfff]|[\ufffe-\uffff]')
===================================== gajim/gtk/control.py ===================================== @@ -502,7 +502,7 @@ def scroll_to_message(self, log_line_id: int, timestamp: float) -> None: row = self.conversation_view.get_row_by_log_line_id(log_line_id) if row is None: # Clear view and reload conversation around timestamp - self._scrolled_view.block_request_signal(True) + self._scrolled_view.block_signals(True) self.reset_view() before, at_after = app.storage.archive.get_conversation_around( self.contact.account, self.contact.jid, timestamp) @@ -512,7 +512,7 @@ def scroll_to_message(self, log_line_id: int, timestamp: float) -> None: GLib.idle_add( self.conversation_view.scroll_to_message_and_highlight, log_line_id) - GLib.idle_add(self._scrolled_view.block_request_signal, False) + GLib.idle_add(self._scrolled_view.block_signals, False)
def _fetch_n_lines_history(self, _scrolled: Gtk.ScrolledWindow, @@ -520,7 +520,7 @@ def _fetch_n_lines_history(self, n_lines: int ) -> None:
- self._scrolled_view.block_request_signal(True) + self._scrolled_view.block_signals(True)
if before: row = self.conversation_view.get_first_message_row() @@ -541,7 +541,7 @@ def _fetch_n_lines_history(self,
if not messages: self._scrolled_view.set_history_complete(before, True) - self._scrolled_view.block_request_signal(False) + self._scrolled_view.block_signals(False) return
self.add_messages(messages) @@ -553,7 +553,7 @@ def _fetch_n_lines_history(self, # if self.conversation_view.reduce_message_count(before): # self._scrolled_view.set_history_complete(before, False)
- self._scrolled_view.block_request_signal(False) + self._scrolled_view.block_signals(False)
def add_messages(self, messages: list[ConversationRow]): for msg in messages:
===================================== gajim/gtk/conversation/scrolled.py ===================================== @@ -14,6 +14,7 @@
from __future__ import annotations
+from typing import Any from typing import Optional
from gi.repository import Gtk @@ -66,7 +67,7 @@ def __init__(self) -> None: self._upper_complete: bool = False self._lower_complete: bool = False self._requesting: Optional[str] = None - self._block_request_signal = False + self._block_signals = False
vadjustment = self.get_vadjustment() vadjustment.connect('notify::upper', self._on_adj_upper_changed) @@ -77,14 +78,16 @@ def __init__(self) -> None: self.set_focus_vadjustment(Gtk.Adjustment())
def clear(self) -> None: - self._block_request_signal = True + self._block_signals = True + self._reset() self._view.clear()
def switch_contact(self, contact: ChatContactT) -> None: - self.reset() + self._block_signals = True + self._reset() self._view.switch_contact(contact) - self._block_request_signal = False - self.emit('request-history', True) + self._block_signals = False + self._emit('request-history', True)
def get_autoscroll(self) -> bool: return self._autoscroll @@ -92,18 +95,25 @@ def get_autoscroll(self) -> bool: def get_view(self) -> ConversationView: return self._view
- def block_request_signal(self, value: bool) -> None: - self._block_request_signal = value + def block_signals(self, value: bool) -> None: + self._block_signals = value
- def reset(self) -> None: + def _emit(self, signal_name: str, *args: Any) -> None: + if not self._block_signals: + self.emit(signal_name, *args) + + def _reset(self) -> None: self._current_upper = 0 self._request_history_at_upper = None self._upper_complete = False self._lower_complete = False self._requesting = None - self._view.reset() self.set_history_complete(True, False)
+ def reset(self) -> None: + self._reset() + self._view.reset() + def set_history_complete(self, before: bool, complete: bool) -> None: if before: self._upper_complete = complete @@ -134,11 +144,11 @@ def _on_adj_upper_changed(self,
if upper == adj.get_page_size(): # There is no scrollbar - if not self._block_request_signal: - self.emit('request-history', True) + if not self._block_signals: + self._emit('request-history', True) self._lower_complete = True self._autoscroll = True - self.emit('autoscroll-changed', self._autoscroll) + self._emit('autoscroll-changed', self._autoscroll)
self._requesting = None
@@ -152,10 +162,10 @@ def _on_adj_value_changed(self, bottom = adj.get_upper() - adj.get_page_size() if (bottom - adj.get_value()) < 1: self._autoscroll = True - self.emit('autoscroll-changed', self._autoscroll) + self._emit('autoscroll-changed', self._autoscroll) else: self._autoscroll = False - self.emit('autoscroll-changed', self._autoscroll) + self._emit('autoscroll-changed', self._autoscroll)
if self._upper_complete: self._request_history_at_upper = None @@ -178,8 +188,8 @@ def _on_adj_value_changed(self, self._request_history_at_upper = adj.get_upper() # Workaround: https://gitlab.gnome.org/GNOME/gtk/merge_requests/395 self.set_kinetic_scrolling(False) - if not self._block_request_signal: - self.emit('request-history', True) + if not self._block_signals: + self._emit('request-history', True) self._requesting = 'before' elif (adj.get_upper() - (adj.get_value() + adj.get_page_size()) < distance): @@ -188,6 +198,6 @@ def _on_adj_value_changed(self, return # Workaround: https://gitlab.gnome.org/GNOME/gtk/merge_requests/395 self.set_kinetic_scrolling(False) - if not self._block_request_signal: - self.emit('request-history', False) + if not self._block_signals: + self._emit('request-history', False) self._requesting = 'after'
===================================== test/no_gui/test_regex.py ===================================== @@ -1,36 +1,44 @@ +from typing import Any + import unittest
-from gajim import gui -gui.init('gtk') -import gajim.common.regex as regex +from gajim.common.styling import URI_RX +from gajim.common.styling import ADDRESS_RX
-class Test(unittest.TestCase): - def test_links_regexp_entire(self): - def assert_matches_all(str_): - m = regex.BASIC_REGEX.match(str_)
- # the match should equal the string - str_span = (0, len(str_)) - self.assertEqual(m.span(), str_span) +TEST_URIS = [ + 'http://google.com/', + 'http://google.com', + 'http://www.google.ca/search?q=xmpp', + 'http://tools.ietf.org/html/draft-saintandre-rfc3920bis-05#section-12.3', + 'http://en.wikipedia.org/wiki/Protocol_(computing)', + 'http://en.wikipedia.org/wiki/Protocol_%28computing%29', +]
- # these entire strings should be parsed as links - assert_matches_all('http://google.com/') - assert_matches_all('http://google.com') - assert_matches_all('http://www.google.ca/search?q=xmpp') +ADDRESSES = [ + 'mailto:test@example.org', + 'xmpp:example-node@example.com', + # 'xmpp:example-node@example.com/some-resource', FIXME + 'xmpp:example-node@example.com?message', +]
- assert_matches_all('http://tools.ietf.org/html/draft-saintandre-rfc3920bis-05#section-12.3')
- assert_matches_all('http://en.wikipedia.org/wiki/Protocol_(computing)') - assert_matches_all( - 'http://en.wikipedia.org/wiki/Protocol_%28computing%29') +class Test(unittest.TestCase): + def test_links_regexp_entire(self): + def assert_matches_all(regex: Any, uri_str: str) -> None: + m = regex.match(uri_str) + if m is None: + raise AssertionError('Failed: %s' % uri_str) + + str_span = (0, len(uri_str)) + self.assertEqual(m.span(), str_span, uri_str)
- assert_matches_all('mailto:test@example.org') + for uri in TEST_URIS: + assert_matches_all(URI_RX, uri)
- assert_matches_all('xmpp:example-node@example.com') - assert_matches_all('xmpp:example-node@example.com/some-resource') - assert_matches_all('xmpp:example-node@example.com?message') - assert_matches_all('xmpp://guest@example.com/support@example.com?message') + for addr in ADDRESSES: + assert_matches_all(ADDRESS_RX, addr)
-if __name__ == "__main__": +if __name__ == '__main__': unittest.main()
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/compare/36578a37723183b7c7d75f7c6e817601...