summaryrefslogtreecommitdiff
path: root/poezio/core/tabs.py
diff options
context:
space:
mode:
Diffstat (limited to 'poezio/core/tabs.py')
-rw-r--r--poezio/core/tabs.py91
1 files changed, 64 insertions, 27 deletions
diff --git a/poezio/core/tabs.py b/poezio/core/tabs.py
index abea7313..6d0589ba 100644
--- a/poezio/core/tabs.py
+++ b/poezio/core/tabs.py
@@ -24,12 +24,14 @@ have become [0|1|2|3], with the tab "4" renumbered to "3" if gap tabs are
disabled.
"""
-from typing import List, Dict, Type, Optional, Union
+from typing import List, Dict, Type, Optional, Union, Tuple, TypeVar, cast
from collections import defaultdict
from slixmpp import JID
from poezio import tabs
from poezio.events import EventHandler
+T = TypeVar('T', bound=tabs.Tab)
+
class Tabs:
"""
@@ -46,23 +48,22 @@ class Tabs:
'_events',
]
- def __init__(self, events: EventHandler) -> None:
+ def __init__(self, events: EventHandler, initial_tab: tabs.Tab) -> None:
"""
Initialize the Tab List. Even though the list is initially
empty, all methods are only valid once append() has been called
once. Otherwise, mayhem is expected.
"""
# cursor
- self._current_index = 0 # type: int
- self._current_tab = None # type: Optional[tabs.Tab]
+ self._current_index: int = 0
+ self._current_tab: tabs.Tab = initial_tab
- self._previous_tab = None # type: Optional[tabs.Tab]
- self._tabs = [] # type: List[tabs.Tab]
- self._tab_jids = dict() # type: Dict[JID, tabs.Tab]
- self._tab_types = defaultdict(
- list) # type: Dict[Type[tabs.Tab], List[tabs.Tab]]
- self._tab_names = dict() # type: Dict[str, tabs.Tab]
- self._events = events # type: EventHandler
+ self._previous_tab: Optional[tabs.Tab] = None
+ self._tabs: List[tabs.Tab] = []
+ self._tab_jids: Dict[JID, tabs.Tab] = dict()
+ self._tab_types: Dict[Type[tabs.Tab], List[tabs.Tab]] = defaultdict(list)
+ self._tab_names: Dict[str, tabs.Tab] = dict()
+ self._events: EventHandler = events
def __len__(self):
return len(self._tabs)
@@ -92,7 +93,7 @@ class Tabs:
return False
@property
- def current_tab(self) -> Optional[tabs.Tab]:
+ def current_tab(self) -> tabs.Tab:
"""Current tab"""
return self._current_tab
@@ -122,9 +123,9 @@ class Tabs:
"""Get a tab with a specific name"""
return self._tab_names.get(name)
- def by_class(self, cls: Type[tabs.Tab]) -> List[tabs.Tab]:
+ def by_class(self, cls: Type[T]) -> List[T]:
"""Get all the tabs of a class"""
- return self._tab_types.get(cls, [])
+ return cast(List[T], self._tab_types.get(cls, []))
def find_match(self, name: str) -> Optional[tabs.Tab]:
"""Get a tab using extended matching (tab.matching_name())"""
@@ -139,13 +140,49 @@ class Tabs:
return self._tabs[i]
return None
- def by_name_and_class(self, name: str,
- cls: Type[tabs.Tab]) -> Optional[tabs.Tab]:
+ def find_by_unique_prefix(self, prefix: str) -> Tuple[bool, Optional[tabs.Tab]]:
+ """
+ Get a tab by its unique name prefix, ignoring case.
+
+ :return: A tuple indicating the presence of any match, as well as the
+ uniquely matched tab (if any).
+
+ The first element, a boolean, in the returned tuple indicates whether
+ at least one tab matched.
+
+ The second element (a Tab) in the returned tuple is the uniquely
+ matched tab, if any. If multiple or no tabs match the prefix, the
+ second element in the tuple is :data:`None`.
+ """
+
+ # TODO: should this maybe use something smarter than .lower()?
+ # something something stringprep?
+ prefix = prefix.lower()
+ candidate = None
+ any_matched = False
+ for tab in self._tabs:
+ if not tab.name.lower().startswith(prefix):
+ continue
+ any_matched = True
+ if candidate is not None:
+ # multiple tabs match -> return None
+ return True, None
+ candidate = tab
+
+ return any_matched, candidate
+
+ def by_name_and_class(self, name: Union[str, JID],
+ cls: Type[T]) -> Optional[T]:
"""Get a tab with its name and class"""
+ if isinstance(name, JID):
+ str_name = name.full
+ else:
+ str_name = name
+ str
cls_tabs = self._tab_types.get(cls, [])
for tab in cls_tabs:
- if tab.name == name:
- return tab
+ if tab.name == str_name:
+ return cast(T, tab)
return None
def _rebuild(self):
@@ -156,7 +193,7 @@ class Tabs:
for cls in _get_tab_types(tab):
self._tab_types[cls].append(tab)
if hasattr(tab, 'jid'):
- self._tab_jids[tab.jid] = tab
+ self._tab_jids[tab.jid] = tab # type: ignore
self._tab_names[tab.name] = tab
self._update_numbers()
@@ -217,7 +254,7 @@ class Tabs:
for cls in _get_tab_types(tab):
self._tab_types[cls].append(tab)
if hasattr(tab, 'jid'):
- self._tab_jids[tab.jid] = tab
+ self._tab_jids[tab.jid] = tab # type: ignore
self._tab_names[tab.name] = tab
def delete(self, tab: tabs.Tab, gap=False):
@@ -226,7 +263,7 @@ class Tabs:
return
if gap:
- self._tabs[tab.nb] = tabs.GapTab(None)
+ self._tabs[tab.nb] = tabs.GapTab()
else:
self._tabs.remove(tab)
@@ -235,7 +272,7 @@ class Tabs:
for cls in _get_tab_types(tab):
self._tab_types[cls].remove(tab)
if hasattr(tab, 'jid'):
- del self._tab_jids[tab.jid]
+ del self._tab_jids[tab.jid] # type: ignore
del self._tab_names[tab.name]
if gap:
@@ -262,7 +299,7 @@ class Tabs:
def _validate_current_index(self):
if not 0 <= self._current_index < len(
self._tabs) or not self.current_tab:
- self.prev()
+ self.prev() # pylint: disable=not-callable
def _collect_trailing_gaptabs(self):
"""Remove trailing gap tabs if any"""
@@ -315,16 +352,16 @@ class Tabs:
if new_pos < len(self._tabs):
old_tab = self._tabs[old_pos]
self._tabs[new_pos], self._tabs[
- old_pos] = old_tab, tabs.GapTab(self)
+ old_pos] = old_tab, tabs.GapTab()
else:
self._tabs.append(self._tabs[old_pos])
- self._tabs[old_pos] = tabs.GapTab(self)
+ self._tabs[old_pos] = tabs.GapTab()
else:
if new_pos > old_pos:
self._tabs.insert(new_pos, tab)
- self._tabs[old_pos] = tabs.GapTab(self)
+ self._tabs[old_pos] = tabs.GapTab()
elif new_pos < old_pos:
- self._tabs[old_pos] = tabs.GapTab(self)
+ self._tabs[old_pos] = tabs.GapTab()
self._tabs.insert(new_pos, tab)
else:
return False