summaryrefslogtreecommitdiff
path: root/poezio
diff options
context:
space:
mode:
Diffstat (limited to 'poezio')
-rw-r--r--poezio/common.py20
-rw-r--r--poezio/config.py1
-rw-r--r--poezio/core/commands.py14
-rw-r--r--poezio/core/core.py35
-rw-r--r--poezio/core/tabs.py34
-rw-r--r--poezio/windows/info_bar.py34
6 files changed, 136 insertions, 2 deletions
diff --git a/poezio/common.py b/poezio/common.py
index ba179310..7cddc306 100644
--- a/poezio/common.py
+++ b/poezio/common.py
@@ -17,6 +17,7 @@ import subprocess
import time
import string
import logging
+import itertools
from slixmpp import JID, InvalidJID, Message
from poezio.poezio_shlex import shlex
@@ -468,3 +469,22 @@ def safeJID(*args, **kwargs) -> JID:
exc_info=True,
)
return JID('')
+
+
+def unique_prefix_of(a: str, b: str) -> str:
+ """
+ Return the unique prefix of `a` with `b`.
+
+ Corner cases:
+
+ * If `a` and `b` share no prefix, the first letter of `a` is returned.
+ * If `a` and `b` are equal, `a` is returned.
+ * If `a` is a prefix of `b`, `a` is returned.
+ * If `b` is a prefix of `a`, `b` plus the first letter of `a` after the
+ common prefix is returned.
+ """
+ for i, (ca, cb) in enumerate(itertools.zip_longest(a, b)):
+ if ca != cb:
+ return a[:i+1]
+ # both are equal, return a
+ return a
diff --git a/poezio/config.py b/poezio/config.py
index 8da71071..09d465cd 100644
--- a/poezio/config.py
+++ b/poezio/config.py
@@ -136,6 +136,7 @@ DEFAULT_CONFIG = {
'theme': 'default',
'themes_dir': '',
'tmp_image_dir': '',
+ 'unique_prefix_tab_names': False,
'use_bookmarks_method': '',
'use_log': True,
'use_remote_bookmarks': True,
diff --git a/poezio/core/commands.py b/poezio/core/commands.py
index 6bf1d338..46dab5cc 100644
--- a/poezio/core/commands.py
+++ b/poezio/core/commands.py
@@ -219,6 +219,20 @@ class CommandCore:
return
self.core.tabs.set_current_tab(match)
+ @command_args_parser.quoted(1)
+ def wup(self, args):
+ """
+ /wup <prefix of name>
+ """
+ if args is None:
+ return self.help('wup')
+
+ prefix = args[0]
+ _, match = self.core.tabs.find_by_unique_prefix(prefix)
+ if match is None:
+ return
+ self.core.tabs.set_current_tab(match)
+
@command_args_parser.quoted(2)
def move_tab(self, args):
"""
diff --git a/poezio/core/core.py b/poezio/core/core.py
index 06d56062..8ac88dd4 100644
--- a/poezio/core/core.py
+++ b/poezio/core/core.py
@@ -209,6 +209,7 @@ class Core:
'_show_plugins': self.command.plugins,
'_show_xmltab': self.command.xml_tab,
'_toggle_pane': self.toggle_left_pane,
+ "_go_to_room_name": self.go_to_room_name,
###### status actions ######
'_available': lambda: self.command.status('available'),
'_away': lambda: self.command.status('away'),
@@ -1108,6 +1109,34 @@ class Core:
keyboard.continuation_keys_callback = read_next_digit
+ def go_to_room_name(self) -> None:
+ room_name_jump = []
+
+ def read_next_letter(s) -> None:
+ nonlocal room_name_jump
+ room_name_jump.append(s)
+ any_matched, unique_tab = self.tabs.find_by_unique_prefix(
+ "".join(room_name_jump)
+ )
+
+ if not any_matched:
+ return
+
+ if unique_tab is not None:
+ self.tabs.set_current_tab(unique_tab)
+ # NOTE: returning here means that as soon as the tab is
+ # matched, normal input resumes. If we do *not* return here,
+ # any further characters matching the prefix of the tab will
+ # be swallowed (and a lot of tab switching will happen...),
+ # until a non-matching character or escape or something is
+ # pressed.
+ # This behaviour *may* be desirable.
+ return
+
+ keyboard.continuation_keys_callback = read_next_letter
+
+ keyboard.continuation_keys_callback = read_next_letter
+
def go_to_roster(self) -> None:
"Select the roster as the current tab"
self.tabs.set_current_tab(self.tabs.first())
@@ -1709,6 +1738,12 @@ class Core:
usage='<number or name>',
shortdesc='Go to the specified room',
completion=self.completion.win)
+ self.register_command(
+ 'wup',
+ self.command.wup,
+ usage='<prefix>',
+ shortdesc='Go to the tab whose name uniquely starts with prefix',
+ completion=self.completion.win)
self.commands['w'] = self.commands['win']
self.register_command(
'move_tab',
diff --git a/poezio/core/tabs.py b/poezio/core/tabs.py
index abea7313..d5909d39 100644
--- a/poezio/core/tabs.py
+++ b/poezio/core/tabs.py
@@ -24,11 +24,12 @@ 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
from collections import defaultdict
from slixmpp import JID
from poezio import tabs
from poezio.events import EventHandler
+from poezio.config import config
class Tabs:
@@ -139,6 +140,37 @@ class Tabs:
return self._tabs[i]
return None
+ 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: str,
cls: Type[tabs.Tab]) -> Optional[tabs.Tab]:
"""Get a tab with its name and class"""
diff --git a/poezio/windows/info_bar.py b/poezio/windows/info_bar.py
index ac900103..e94e1810 100644
--- a/poezio/windows/info_bar.py
+++ b/poezio/windows/info_bar.py
@@ -6,6 +6,7 @@ The GlobalInfoBar can be either horizontal or vertical
(VerticalGlobalInfoBar).
"""
import logging
+import itertools
log = logging.getLogger(__name__)
import curses
@@ -13,6 +14,7 @@ import curses
from poezio.config import config
from poezio.windows.base_wins import Win
from poezio.theming import get_theme, to_curses_attr
+from poezio.common import unique_prefix_of
class GlobalInfoBar(Win):
@@ -33,6 +35,34 @@ class GlobalInfoBar(Win):
show_nums = config.get('show_tab_numbers')
use_nicks = config.get('use_tab_nicks')
show_inactive = config.get('show_inactive_tabs')
+ unique_prefix_tab_names = config.get('unique_prefix_tab_names')
+
+ if unique_prefix_tab_names:
+ unique_prefixes = [None] * len(self.core.tabs)
+ sorted_tab_indices = sorted(
+ (str(tab.name), i)
+ for i, tab in enumerate(self.core.tabs)
+ )
+ prev_name = ""
+ for (name, i), next_item in itertools.zip_longest(
+ sorted_tab_indices, sorted_tab_indices[1:]):
+ # TODO: should this maybe use something smarter than .lower()?
+ # something something stringprep?
+ name = name.lower()
+ prefix_prev = unique_prefix_of(name, prev_name)
+ if next_item is not None:
+ prefix_next = unique_prefix_of(name, next_item[0].lower())
+ else:
+ prefix_next = name[0]
+
+ # to be unique, we have to use the longest prefix
+ if len(prefix_next) > len(prefix_prev):
+ prefix = prefix_next
+ else:
+ prefix = prefix_prev
+
+ unique_prefixes[i] = prefix
+ prev_name = name
for nb, tab in enumerate(self.core.tabs):
if not tab:
@@ -46,7 +76,9 @@ class GlobalInfoBar(Win):
if show_names:
self.addstr(' ', to_curses_attr(color))
if show_names:
- if use_nicks:
+ if unique_prefix_tab_names:
+ self.addstr(unique_prefixes[nb], to_curses_attr(color))
+ elif use_nicks:
self.addstr("%s" % str(tab.get_nick()),
to_curses_attr(color))
else: