diff options
Diffstat (limited to 'poezio/core/tabs.py')
-rw-r--r-- | poezio/core/tabs.py | 91 |
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 |