completions.py 18.7 KB
Newer Older
mathieui's avatar
mathieui committed
1 2 3 4
"""
Completions for the global commands
"""
import logging
5
from typing import List, Optional
mathieui's avatar
mathieui committed
6 7 8 9

log = logging.getLogger(__name__)

import os
10
from pathlib import Path
mathieui's avatar
mathieui committed
11 12
from functools import reduce

13 14
from slixmpp import JID

15 16 17
from poezio import common
from poezio import pep
from poezio import tabs
18
from poezio import xdg
19 20 21
from poezio.common import safeJID
from poezio.config import config
from poezio.roster import roster
mathieui's avatar
mathieui committed
22

23
from poezio.core.structs import POSSIBLE_SHOW, Completion
mathieui's avatar
mathieui committed
24

mathieui's avatar
mathieui committed
25

26 27 28 29
class CompletionCore:
    def __init__(self, core):
        self.core = core

30
    def help(self, the_input):
31
        """Completion for /help."""
mathieui's avatar
mathieui committed
32
        commands = sorted(self.core.commands.keys()) + sorted(
33
            self.core.tabs.current_tab.commands.keys())
34
        return Completion(the_input.new_completion, commands, 1, quotify=False)
35

36
    def status(self, the_input):
37 38 39 40
        """
        Completion of /status
        """
        if the_input.get_argument_position() == 1:
mathieui's avatar
mathieui committed
41 42 43 44 45
            return Completion(
                the_input.new_completion, [status for status in POSSIBLE_SHOW],
                1,
                ' ',
                quotify=False)
46

47 48 49 50 51 52 53 54
    def roster_barejids(self, the_input):
        """Complete roster bare jids"""
        jids = sorted(
            str(contact.bare_jid) for contact in roster.contacts.values()
            if contact.pending_in
        )
        return Completion(the_input.new_completion, jids, 1, '', quotify=False)

55 56 57 58 59
    def remove(self, the_input):
        """Completion for /remove"""
        jids = [jid for jid in roster.jids()]
        return Completion(the_input.auto_completion, jids, '', quotify=False)

60
    def presence(self, the_input):
61 62 63 64 65
        """
        Completion of /presence
        """
        arg = the_input.get_argument_position()
        if arg == 1:
66 67
            to_suggest = []
            for bookmark in self.core.bookmarks:
68 69
                tab = self.core.tabs.by_name_and_class(bookmark.jid,
                                                       tabs.MucTab)
70 71
                if tab is not None and tab.joined:
                    to_suggest.append(bookmark.jid)
mathieui's avatar
mathieui committed
72
            return Completion(
mathieui's avatar
mathieui committed
73 74
                the_input.auto_completion,
                roster.jids() + to_suggest,
mathieui's avatar
mathieui committed
75 76
                '',
                quotify=True)
77
        elif arg == 2:
mathieui's avatar
mathieui committed
78 79 80 81 82
            return Completion(
                the_input.auto_completion,
                [status for status in POSSIBLE_SHOW],
                '',
                quotify=True)
83

84
    def theme(self, the_input):
85 86
        """ Completion for /theme"""
        themes_dir = config.get('themes_dir')
mathieui's avatar
mathieui committed
87 88
        themes_dir = Path(themes_dir).expanduser(
        ) if themes_dir else xdg.DATA_HOME / 'themes'
89
        try:
90 91 92 93
            theme_files = [
                name.stem for name in themes_dir.iterdir()
                if name.suffix == '.py' and name.name != '__init__.py'
            ]
94
        except OSError:
95
            log.error('Completion for /theme failed', exc_info=True)
96 97
            return False
        if 'default' not in theme_files:
98
            theme_files.append('default')
mathieui's avatar
mathieui committed
99 100
        return Completion(
            the_input.new_completion, theme_files, 1, '', quotify=False)
101

102
    def win(self, the_input):
103 104
        """Completion for /win"""
        l = []
105
        for tab in self.core.tabs:
106 107
            l.extend(tab.matching_names())
        l = [i[1] for i in l]
108
        return Completion(the_input.new_completion, l, 1, '', quotify=False)
109

110
    def join(self, the_input):
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
        """
        Completion for /join

        Try to complete the MUC JID:
            if only a resource is provided, complete with the default nick
            if only a server is provided, complete with the rooms from the
                disco#items of that server
            if only a nodepart is provided, complete with the servers of the
                current joined rooms
        """
        n = the_input.get_argument_position(quoted=True)
        args = common.shell_split(the_input.text)
        if n != 1:
            # we are not on the 1st argument of the command line
            return False
        if len(args) == 1:
            args.append('')
        jid = safeJID(args[1])
        if args[1].endswith('@') and not jid.user and not jid.server:
            jid.user = args[1][:-1]

        relevant_rooms = []
133
        relevant_rooms.extend(sorted(self.core.pending_invites.keys()))
mathieui's avatar
mathieui committed
134 135 136
        bookmarks = [(str(elem.jid)
                      if not elem.nick else '%s/%s' % (elem.jid, elem.nick))
                     for elem in self.core.bookmarks]
mathieui's avatar
mathieui committed
137 138
        to_suggest = []
        for bookmark in bookmarks:
139
            tab = self.core.tabs.by_name_and_class(bookmark, tabs.MucTab)
mathieui's avatar
mathieui committed
140 141 142
            if not tab or (tab and not tab.joined):
                to_suggest.append(bookmark)
        relevant_rooms.extend(sorted(to_suggest))
143 144

        if the_input.last_completion:
145
            return Completion(the_input.new_completion, [], 1, quotify=True)
146 147 148 149

        if jid.user:
            # we are writing the server: complete the server
            serv_list = []
150
            for tab in self.core.get_tabs(tabs.MucTab):
151
                if tab.joined:
mathieui's avatar
mathieui committed
152 153
                    serv_list.append(
                        '%s@%s' % (jid.user, safeJID(tab.name).host))
154
            serv_list.extend(relevant_rooms)
mathieui's avatar
mathieui committed
155 156
            return Completion(
                the_input.new_completion, serv_list, 1, quotify=True)
157 158
        elif args[1].startswith('/'):
            # we completing only a resource
mathieui's avatar
mathieui committed
159 160 161 162
            return Completion(
                the_input.new_completion, ['/%s' % self.core.own_nick],
                1,
                quotify=True)
mathieui's avatar
mathieui committed
163
        else:
mathieui's avatar
mathieui committed
164 165
            return Completion(
                the_input.new_completion, relevant_rooms, 1, quotify=True)
166

167
    def version(self, the_input):
168
        """Completion for /version"""
mathieui's avatar
mathieui committed
169 170 171 172 173
        comp = reduce(lambda x, y: x + [i.jid for i in y],
                      (roster[jid].resources for jid in roster.jids()
                       if len(roster[jid])), [])
        return Completion(
            the_input.new_completion, sorted(comp), 1, quotify=False)
174

175
    def list(self, the_input):
176 177
        """Completion for /list"""
        muc_serv_list = []
mathieui's avatar
mathieui committed
178 179
        for tab in self.core.get_tabs(
                tabs.MucTab):  # TODO, also from an history
180 181
            if tab.jid.server not in muc_serv_list:
                muc_serv_list.append(tab.jid.server)
182
        if muc_serv_list:
mathieui's avatar
mathieui committed
183 184
            return Completion(
                the_input.new_completion, muc_serv_list, 1, quotify=False)
185

186
    def move_tab(self, the_input):
187 188 189
        """Completion for /move_tab"""
        n = the_input.get_argument_position(quoted=True)
        if n == 1:
190
            nodes = [tab.name for tab in self.core.tabs if tab]
191
            nodes.remove('Roster')
mathieui's avatar
mathieui committed
192 193
            return Completion(
                the_input.new_completion, nodes, 1, ' ', quotify=True)
194

195
    def runkey(self, the_input):
196 197 198 199
        """
        Completion for /runkey
        """
        list_ = []
200
        list_.extend(self.core.key_func.keys())
201
        list_.extend(self.core.tabs.current_tab.key_func.keys())
202
        return Completion(the_input.new_completion, list_, 1, quotify=False)
203

204
    def bookmark(self, the_input):
205 206 207 208 209
        """Completion for /bookmark"""
        args = common.shell_split(the_input.text)
        n = the_input.get_argument_position(quoted=True)

        if n == 2:
mathieui's avatar
mathieui committed
210 211
            return Completion(
                the_input.new_completion, ['true', 'false'], 2, quotify=True)
212
        if n >= 3:
213
            return False
214 215 216 217 218 219

        if len(args) == 1:
            args.append('')
        jid = safeJID(args[1])

        if jid.server and (jid.resource or jid.full.endswith('/')):
220
            tab = self.core.tabs.by_name_and_class(jid.bare, tabs.MucTab)
221
            nicks = [tab.own_nick] if tab else []
mathieui's avatar
mathieui committed
222 223
            default = os.environ.get('USER') if os.environ.get(
                'USER') else 'poezio'
224 225
            nick = config.get('default_nick')
            if not nick:
226
                if default not in nicks:
227
                    nicks.append(default)
mathieui's avatar
mathieui committed
228
            else:
229
                if nick not in nicks:
230 231
                    nicks.append(nick)
            jids_list = ['%s/%s' % (jid.bare, nick) for nick in nicks]
mathieui's avatar
mathieui committed
232 233
            return Completion(
                the_input.new_completion, jids_list, 1, quotify=True)
234
        muc_list = [tab.name for tab in self.core.get_tabs(tabs.MucTab)]
235 236
        muc_list.sort()
        muc_list.append('*')
237
        return Completion(the_input.new_completion, muc_list, 1, quotify=True)
238

239
    def remove_bookmark(self, the_input):
240
        """Completion for /remove_bookmark"""
mathieui's avatar
mathieui committed
241 242 243 244
        return Completion(
            the_input.new_completion, [bm.jid for bm in self.core.bookmarks],
            1,
            quotify=False)
245

246
    def decline(self, the_input):
247 248 249
        """Completion for /decline"""
        n = the_input.get_argument_position(quoted=True)
        if n == 1:
mathieui's avatar
mathieui committed
250 251 252 253 254 255
            return Completion(
                the_input.auto_completion,
                sorted(self.core.pending_invites.keys()),
                1,
                '',
                quotify=True)
256

257
    def bind(self, the_input):
258 259
        n = the_input.get_argument_position()
        if n == 1:
mathieui's avatar
mathieui committed
260 261 262
            args = [
                key for key in self.core.key_func if not key.startswith('_')
            ]
263
        elif n == 2:
264
            args = [key for key in self.core.key_func]
mathieui's avatar
mathieui committed
265
        else:
266
            return False
267

268
        return Completion(the_input.new_completion, args, n, '', quotify=False)
269

270
    def message(self, the_input):
271 272 273
        """Completion for /message"""
        n = the_input.get_argument_position(quoted=True)
        if n >= 2:
274
            return False
275 276 277 278 279 280 281 282 283
        online = []
        offline = []
        for jid in sorted(roster.jids()):
            if len(roster[jid]) > 0:
                online.append(jid)
            else:
                offline.append(jid)
        return Completion(
            the_input.new_completion, online + offline, 1, '', quotify=True)
284

285
    def invite(self, the_input):
286 287 288
        """Completion for /invite"""
        n = the_input.get_argument_position(quoted=True)
        if n == 1:
mathieui's avatar
mathieui committed
289 290 291
            comp = reduce(lambda x, y: x + [i.jid for i in y],
                          (roster[jid].resources for jid in roster.jids()
                           if len(roster[jid])), [])
292
            comp = sorted(comp)
mathieui's avatar
mathieui committed
293 294 295
            bares = sorted(roster[contact].bare_jid
                           for contact in roster.jids()
                           if len(roster[contact]))
296 297
            off = sorted(jid for jid in roster.jids() if jid not in bares)
            comp = comp + bares + off
298
            return Completion(the_input.new_completion, comp, n, quotify=True)
299 300
        elif n == 2:
            rooms = []
301
            for tab in self.core.get_tabs(tabs.MucTab):
302
                if tab.joined:
303
                    rooms.append(tab.jid.bare)
304
            rooms.sort()
mathieui's avatar
mathieui committed
305 306
            return Completion(
                the_input.new_completion, rooms, n, '', quotify=True)
307

Maxime Buquet's avatar
Maxime Buquet committed
308 309 310 311 312 313 314 315 316 317 318 319 320
    def impromptu(self, the_input):
        """Completion for /impromptu"""
        n = the_input.get_argument_position(quoted=True)
        onlines = []
        offlines = []
        for barejid in roster.jids():
            if len(roster[barejid]):
                onlines.append(barejid)
            else:
                offlines.append(barejid)
        comp = sorted(onlines) + sorted(offlines)
        return Completion(the_input.new_completion, comp, n, quotify=True)

321
    def activity(self, the_input):
322 323 324 325
        """Completion for /activity"""
        n = the_input.get_argument_position(quoted=True)
        args = common.shell_split(the_input.text)
        if n == 1:
mathieui's avatar
mathieui committed
326 327 328 329 330
            return Completion(
                the_input.new_completion,
                sorted(pep.ACTIVITIES.keys()),
                n,
                quotify=True)
331 332 333 334 335
        elif n == 2:
            if args[1] in pep.ACTIVITIES:
                l = list(pep.ACTIVITIES[args[1]])
                l.remove('category')
                l.sort()
336
                return Completion(the_input.new_completion, l, n, quotify=True)
337

338
    def mood(self, the_input):
339 340 341
        """Completion for /mood"""
        n = the_input.get_argument_position(quoted=True)
        if n == 1:
mathieui's avatar
mathieui committed
342 343 344 345 346
            return Completion(
                the_input.new_completion,
                sorted(pep.MOODS.keys()),
                1,
                quotify=True)
347

348
    def last_activity(self, the_input):
349 350 351 352 353
        """
        Completion for /last_activity <jid>
        """
        n = the_input.get_argument_position(quoted=False)
        if n >= 2:
354
            return False
mathieui's avatar
mathieui committed
355 356 357 358 359
        comp = reduce(lambda x, y: x + [i.jid for i in y],
                      (roster[jid].resources for jid in roster.jids()
                       if len(roster[jid])), [])
        return Completion(
            the_input.new_completion, sorted(comp), 1, '', quotify=False)
360

361
    def server_cycle(self, the_input):
362 363
        """Completion for /server_cycle"""
        serv_list = set()
364
        for tab in self.core.get_tabs(tabs.MucTab):
365
            serv_list.add(tab.jid.server)
366
        return Completion(the_input.new_completion, sorted(serv_list), 1, ' ')
367

368
    def set(self, the_input):
369 370 371 372 373 374 375 376
        """Completion for /set"""
        args = common.shell_split(the_input.text)
        n = the_input.get_argument_position(quoted=True)
        if n >= len(args):
            args.append('')
        if n == 1:
            if '|' in args[1]:
                plugin_name, section = args[1].split('|')[:2]
377
                if plugin_name not in self.core.plugin_manager.plugins:
mathieui's avatar
mathieui committed
378 379
                    return Completion(
                        the_input.new_completion, [], n, quotify=True)
380
                plugin = self.core.plugin_manager.plugins[plugin_name]
mathieui's avatar
mathieui committed
381 382 383 384
                end_list = [
                    '%s|%s' % (plugin_name, section)
                    for section in plugin.config.sections()
                ]
mathieui's avatar
mathieui committed
385
            else:
386 387 388 389 390 391 392
                end_list = set(config.options('Poezio'))
                end_list.update(config.default.get('Poezio', {}))
                end_list = list(end_list)
                end_list.sort()
        elif n == 2:
            if '|' in args[1]:
                plugin_name, section = args[1].split('|')[:2]
393
                if plugin_name not in self.core.plugin_manager.plugins:
mathieui's avatar
mathieui committed
394 395
                    return Completion(
                        the_input.new_completion, [''], n, quotify=True)
396
                plugin = self.core.plugin_manager.plugins[plugin_name]
397 398
                end_list = set(plugin.config.options(section or plugin_name))
                if plugin.config.default:
mathieui's avatar
mathieui committed
399 400
                    end_list.update(
                        plugin.config.default.get(section or plugin_name, {}))
401 402 403 404 405 406 407 408 409 410 411 412 413
                end_list = list(end_list)
                end_list.sort()
            elif not config.has_option('Poezio', args[1]):
                if config.has_section(args[1]):
                    end_list = config.options(args[1])
                    end_list.append('')
                else:
                    end_list = []
            else:
                end_list = [str(config.get(args[1], '')), '']
        elif n == 3:
            if '|' in args[1]:
                plugin_name, section = args[1].split('|')[:2]
414
                if plugin_name not in self.core.plugin_manager.plugins:
mathieui's avatar
mathieui committed
415 416
                    return Completion(
                        the_input.new_completion, [''], n, quotify=True)
417
                plugin = self.core.plugin_manager.plugins[plugin_name]
mathieui's avatar
mathieui committed
418 419 420 421 422
                end_list = [
                    str(
                        plugin.config.get(args[2], '', section
                                          or plugin_name)), ''
                ]
423 424 425 426 427
            else:
                if not config.has_section(args[1]):
                    end_list = ['']
                else:
                    end_list = [str(config.get(args[2], '', args[1])), '']
mathieui's avatar
mathieui committed
428
        else:
429
            return False
430
        return Completion(the_input.new_completion, end_list, n, quotify=True)
431

432
    def set_default(self, the_input):
433 434 435 436 437 438 439
        """ Completion for /set_default
        """
        args = common.shell_split(the_input.text)
        n = the_input.get_argument_position(quoted=True)
        if n >= len(args):
            args.append('')
        if n == 1 or (n == 2 and config.has_section(args[1])):
440
            return Completion(self.set, the_input)
441
        return False
442

443
    def toggle(self, the_input):
444
        "Completion for /toggle"
mathieui's avatar
mathieui committed
445 446 447 448 449
        return Completion(
            the_input.new_completion,
            config.options('Poezio'),
            1,
            quotify=False)
450

451
    def bookmark_local(self, the_input):
452 453 454 455 456
        """Completion for /bookmark_local"""
        n = the_input.get_argument_position(quoted=True)
        args = common.shell_split(the_input.text)

        if n >= 2:
457
            return False
458 459 460 461 462
        if len(args) == 1:
            args.append('')
        jid = safeJID(args[1])

        if jid.server and (jid.resource or jid.full.endswith('/')):
463
            tab = self.core.tabs.by_name_and_class(jid.bare, tabs.MucTab)
464
            nicks = [tab.own_nick] if tab else []
mathieui's avatar
mathieui committed
465 466
            default = os.environ.get('USER') if os.environ.get(
                'USER') else 'poezio'
467 468
            nick = config.get('default_nick')
            if not nick:
469
                if default not in nicks:
470 471
                    nicks.append(default)
            else:
472
                if nick not in nicks:
473 474
                    nicks.append(nick)
            jids_list = ['%s/%s' % (jid.bare, nick) for nick in nicks]
mathieui's avatar
mathieui committed
475 476
            return Completion(
                the_input.new_completion, jids_list, 1, quotify=True)
477
        muc_list = [tab.name for tab in self.core.get_tabs(tabs.MucTab)]
478
        muc_list.append('*')
479
        return Completion(the_input.new_completion, muc_list, 1, quotify=True)
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516

    def block(self, the_input) -> Optional[Completion]:
        """
        Completion for /block
        """
        if the_input.get_argument_position() == 1:

            current_tab = self.core.tabs.current_tab
            chattabs = (
                tabs.ConversationTab,
                tabs.StaticConversationTab,
                tabs.DynamicConversationTab,
            )
            tabjid = []  # type: List[JID]
            if isinstance(current_tab, chattabs):
                tabjid = [current_tab.jid.bare]

            jids = roster.jids()
            jids += tabjid
            return Completion(
                the_input.new_completion, jids, 1, '', quotify=False)
        return None

    def unblock(self, the_input) -> Optional[Completion]:
        """
        Completion for /unblock
        """

        def on_result(iq):
            if iq['type'] == 'error':
                return None
            l = sorted(str(item) for item in iq['blocklist']['items'])
            return Completion(the_input.new_completion, l, 1, quotify=False)

        if the_input.get_argument_position():
            self.core.xmpp.plugin['xep_0191'].get_blocked(callback=on_result)
        return None