home · contact · privacy
Also treat Channel.user_ids as (completable) set rather than sorted.
authorChristian Heller <c.heller@plomlompom.de>
Thu, 11 Sep 2025 17:27:35 +0000 (19:27 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Thu, 11 Sep 2025 17:27:35 +0000 (19:27 +0200)
ircplom/client.py
ircplom/client_tui.py

index 3b7115848a9f822bf55cd086df7b7fc5bd2cca81..6f324a6e9aac89ece904a0f2f0012c04bf44de9b 100644 (file)
@@ -148,11 +148,27 @@ class _CompletableStringsSet(_CompletableStringsCollection, Set):
     def _copy_collected(self) -> set[str]:
         return set(self._collected)
 
-    def add(self, item: str) -> None:
+    def _on_set(self, m_name: str, item: str, on_complete: bool, exists: bool
+                ) -> None:
+        method = getattr(self._collected, m_name)
+        if on_complete:
+            assert self._completed is not None
+            assert (item in self._completed) == exists
+            assert (item in self._collected) == exists
+            method(item)
+            self.complete()
+        else:
+            assert self._completed is None
+            assert (item in self._collected) == exists
+            method(item)
+
+    def completable_add(self, item: str, on_complete: bool) -> None:
         'Put item into collection.'
-        assert self._completed is None
-        assert item not in self._collected
-        self._collected.add(item)
+        self._on_set('add', item, on_complete, False)
+
+    def completable_remove(self, item: str, on_complete: bool) -> None:
+        'Remove item from collection.'
+        self._on_set('remove', item, on_complete, True)
 
     def intersection(self, *others: Iterable[str]) -> set[str]:
         'Compare self to other set(s).'
@@ -177,13 +193,6 @@ class _CompletableStringsOrdered(_Clearable, _CompletableStringsCollection):
         if complete:
             self.complete()
 
-    def remove(self, value: str, complete=False) -> None:
-        'Remove value from list.'
-        assert value in self._collected
-        self._collected = tuple(x for x in self._collected if x != value)
-        if complete:
-            self.complete()
-
     def clear(self) -> None:
         self._completed = None
         self._collected = tuple()
@@ -196,7 +205,7 @@ class IntoUpdateValueMixin(AutoAttrMixin):
         'Return non-updating copy of self.'
         if isinstance(self, _Dict):
             return None
-        if isinstance(self, _CompletableStringsOrdered):
+        if isinstance(self, _CompletableStringsCollection):
             return self._completed
         if isinstance(self, _CompletableTopic):
             return Topic(*self._completed)
@@ -265,8 +274,13 @@ class _UpdatingCompletable(_UpdatingMixin, _Completable):
         self._on_update()
 
 
-class _UpdatingCompletableStringsOrdered(_UpdatingCompletable,
-                                         _CompletableStringsOrdered):
+class _UpdatingCompletableStringsSet(
+        _UpdatingCompletable, _CompletableStringsSet):
+    pass
+
+
+class _UpdatingCompletableStringsOrdered(
+        _UpdatingCompletable, _CompletableStringsOrdered):
     pass
 
 
@@ -392,7 +406,7 @@ class _CompletableTopic(_Completable):
 
 
 class _Channel:
-    user_ids: _CompletableStringsOrdered
+    user_ids: _CompletableStringsSet
     topic: _CompletableTopic
 
     def __init__(self,
@@ -411,17 +425,17 @@ class _Channel:
         for item in items:
             n_u_h = NickUserHost(item.lstrip(self._get_membership_prefixes()))
             user_id = self._userid_for_nickuserhost(n_u_h, create_if_none=True)
-            self.user_ids.append(user_id, complete=False)
+            self.user_ids.completable_add(user_id, on_complete=False)
 
-    def append_user(self, user: '_User') -> None:
-        'To .user_ids append user.nickname and declare .user_ids complete.'
+    def add_user(self, user: '_User') -> None:
+        'To .user_ids add user.nickname, keep .user_ids declared complete.'
         user_id = self._userid_for_nickuserhost(user, create_if_none=True,
                                                 updating=True)
-        self.user_ids.append(user_id, complete=True)
+        self.user_ids.completable_add(user_id, on_complete=True)
 
     def remove_user(self, user: '_User') -> None:
-        'From .user_ids remove .nickname and declare .user_ids complete.'
-        self.user_ids.remove(user.id_, complete=True)
+        'From .user_ids remove .nickname, keep .user_ids declared complete.'
+        self.user_ids.completable_remove(user.id_, on_complete=True)
         self.purge_users()
 
 
@@ -511,7 +525,7 @@ class _UpdatingCompletableTopic(_UpdatingCompletable, _CompletableTopic):
 
 
 class _UpdatingChannel(_UpdatingMixin, _Channel):
-    user_ids: _UpdatingCompletableStringsOrdered
+    user_ids: _UpdatingCompletableStringsSet
     topic: _UpdatingCompletableTopic
 
 
@@ -686,7 +700,7 @@ class _CapsManager(_Clearable):
         if verb in {'LS', 'LIST'}:
             target = getattr(self, f'_{verb.lower()}')
             for item in items:
-                target.add(item)
+                target.completable_add(item, False)
             if complete:
                 target.complete()
                 if target is self._ls:
@@ -856,7 +870,7 @@ class Client(ABC, ClientQueueMixin):
                 else:
                     self.caps.end_negotiation()
         elif ret['_verb'] == 'JOIN' and ret['joiner'] != self.db.users['me']:
-            self.db.channels[ret['channel']].append_user(ret['joiner'])
+            self.db.channels[ret['channel']].add_user(ret['joiner'])
         elif ret['_verb'] == 'NICK':
             user_id = self.db.users.id_for_nickuserhost(ret['named'],
                                                         updating=True)
index 77c66e721918ea8c75bfa76e46f9ae53b5516fb3..e9e02348770a0199c2a7bb3713042bd584cb86ab 100644 (file)
@@ -216,7 +216,7 @@ class _QueryWindow(_ChatWindow):
 
 
 class _UpdatingChannel(_UpdatingNode):
-    user_ids: tuple[str, ...] = tuple()
+    user_ids: set[str]
     topic: Topic = Topic()
     log_scopes = {'': LogScope.CHAT}
 
@@ -232,7 +232,7 @@ class _UpdatingChannel(_UpdatingNode):
         assert update.key == 'user_ids'
         if update.value is None:
             return None
-        assert isinstance(update.value, tuple)
+        assert isinstance(update.value, set)
         d = {'NUHS:joining': tuple(id_ for id_ in update.value
                                    if id_ not in self.user_ids)
              } if self.user_ids else {'NICKS:residents': tuple(update.value)}