completions.py 17 KB
Newer Older
mathieui's avatar
mathieui committed
1 2 3 4 5 6 7 8
"""
Completions for the global commands
"""
import logging

log = logging.getLogger(__name__)

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

12 13 14
from poezio import common
from poezio import pep
from poezio import tabs
15
from poezio import xdg
16 17 18
from poezio.common import safeJID
from poezio.config import config
from poezio.roster import roster
mathieui's avatar
mathieui committed
19

20
from poezio.core.structs import POSSIBLE_SHOW, Completion
mathieui's avatar
mathieui committed
21

mathieui's avatar
mathieui committed
22

23 24 25 26
class CompletionCore:
    def __init__(self, core):
        self.core = core

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

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

44
    def presence(self, the_input):
45 46 47 48 49
        """
        Completion of /presence
        """
        arg = the_input.get_argument_position()
        if arg == 1:
50 51
            to_suggest = []
            for bookmark in self.core.bookmarks:
52 53
                tab = self.core.tabs.by_name_and_class(bookmark.jid,
                                                       tabs.MucTab)
54 55
                if tab is not None and tab.joined:
                    to_suggest.append(bookmark.jid)
mathieui's avatar
mathieui committed
56
            return Completion(
mathieui's avatar
mathieui committed
57 58
                the_input.auto_completion,
                roster.jids() + to_suggest,
mathieui's avatar
mathieui committed
59 60
                '',
                quotify=True)
61
        elif arg == 2:
mathieui's avatar
mathieui committed
62 63 64 65 66
            return Completion(
                the_input.auto_completion,
                [status for status in POSSIBLE_SHOW],
                '',
                quotify=True)
67

68
    def theme(self, the_input):
69 70
        """ Completion for /theme"""
        themes_dir = config.get('themes_dir')
mathieui's avatar
mathieui committed
71 72
        themes_dir = Path(themes_dir).expanduser(
        ) if themes_dir else xdg.DATA_HOME / 'themes'
73
        try:
74 75 76 77
            theme_files = [
                name.stem for name in themes_dir.iterdir()
                if name.suffix == '.py' and name.name != '__init__.py'
            ]
78
        except OSError:
79
            log.error('Completion for /theme failed', exc_info=True)
80 81
            return False
        if 'default' not in theme_files:
82
            theme_files.append('default')
mathieui's avatar
mathieui committed
83 84
        return Completion(
            the_input.new_completion, theme_files, 1, '', quotify=False)
85

86
    def win(self, the_input):
87 88
        """Completion for /win"""
        l = []
89
        for tab in self.core.tabs:
90 91
            l.extend(tab.matching_names())
        l = [i[1] for i in l]
92
        return Completion(the_input.new_completion, l, 1, '', quotify=False)
93

94
    def join(self, the_input):
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
        """
        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 = []
117
        relevant_rooms.extend(sorted(self.core.pending_invites.keys()))
mathieui's avatar
mathieui committed
118 119 120
        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
121 122
        to_suggest = []
        for bookmark in bookmarks:
123
            tab = self.core.tabs.by_name_and_class(bookmark, tabs.MucTab)
mathieui's avatar
mathieui committed
124 125 126
            if not tab or (tab and not tab.joined):
                to_suggest.append(bookmark)
        relevant_rooms.extend(sorted(to_suggest))
127 128

        if the_input.last_completion:
129
            return Completion(the_input.new_completion, [], 1, quotify=True)
130 131 132 133

        if jid.user:
            # we are writing the server: complete the server
            serv_list = []
134
            for tab in self.core.get_tabs(tabs.MucTab):
135
                if tab.joined:
mathieui's avatar
mathieui committed
136 137
                    serv_list.append(
                        '%s@%s' % (jid.user, safeJID(tab.name).host))
138
            serv_list.extend(relevant_rooms)
mathieui's avatar
mathieui committed
139 140
            return Completion(
                the_input.new_completion, serv_list, 1, quotify=True)
141 142
        elif args[1].startswith('/'):
            # we completing only a resource
mathieui's avatar
mathieui committed
143 144 145 146
            return Completion(
                the_input.new_completion, ['/%s' % self.core.own_nick],
                1,
                quotify=True)
mathieui's avatar
mathieui committed
147
        else:
mathieui's avatar
mathieui committed
148 149
            return Completion(
                the_input.new_completion, relevant_rooms, 1, quotify=True)
150

151
    def version(self, the_input):
152
        """Completion for /version"""
mathieui's avatar
mathieui committed
153 154 155 156 157
        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)
158

159
    def list(self, the_input):
160 161
        """Completion for /list"""
        muc_serv_list = []
mathieui's avatar
mathieui committed
162 163
        for tab in self.core.get_tabs(
                tabs.MucTab):  # TODO, also from an history
164 165 166
            if tab.name not in muc_serv_list:
                muc_serv_list.append(safeJID(tab.name).server)
        if muc_serv_list:
mathieui's avatar
mathieui committed
167 168
            return Completion(
                the_input.new_completion, muc_serv_list, 1, quotify=False)
169

170
    def move_tab(self, the_input):
171 172 173
        """Completion for /move_tab"""
        n = the_input.get_argument_position(quoted=True)
        if n == 1:
174
            nodes = [tab.name for tab in self.core.tabs if tab]
175
            nodes.remove('Roster')
mathieui's avatar
mathieui committed
176 177
            return Completion(
                the_input.new_completion, nodes, 1, ' ', quotify=True)
178

179
    def runkey(self, the_input):
180 181 182 183
        """
        Completion for /runkey
        """
        list_ = []
184
        list_.extend(self.core.key_func.keys())
185
        list_.extend(self.core.tabs.current_tab.key_func.keys())
186
        return Completion(the_input.new_completion, list_, 1, quotify=False)
187

188
    def bookmark(self, the_input):
189 190 191 192 193
        """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
194 195
            return Completion(
                the_input.new_completion, ['true', 'false'], 2, quotify=True)
196
        if n >= 3:
197
            return False
198 199 200 201 202 203

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

        if jid.server and (jid.resource or jid.full.endswith('/')):
204
            tab = self.core.tabs.by_name_and_class(jid.bare, tabs.MucTab)
205
            nicks = [tab.own_nick] if tab else []
mathieui's avatar
mathieui committed
206 207
            default = os.environ.get('USER') if os.environ.get(
                'USER') else 'poezio'
208 209
            nick = config.get('default_nick')
            if not nick:
210
                if default not in nicks:
211
                    nicks.append(default)
mathieui's avatar
mathieui committed
212
            else:
213
                if nick not in nicks:
214 215
                    nicks.append(nick)
            jids_list = ['%s/%s' % (jid.bare, nick) for nick in nicks]
mathieui's avatar
mathieui committed
216 217
            return Completion(
                the_input.new_completion, jids_list, 1, quotify=True)
218
        muc_list = [tab.name for tab in self.core.get_tabs(tabs.MucTab)]
219 220
        muc_list.sort()
        muc_list.append('*')
221
        return Completion(the_input.new_completion, muc_list, 1, quotify=True)
222

223
    def remove_bookmark(self, the_input):
224
        """Completion for /remove_bookmark"""
mathieui's avatar
mathieui committed
225 226 227 228
        return Completion(
            the_input.new_completion, [bm.jid for bm in self.core.bookmarks],
            1,
            quotify=False)
229

230
    def decline(self, the_input):
231 232 233
        """Completion for /decline"""
        n = the_input.get_argument_position(quoted=True)
        if n == 1:
mathieui's avatar
mathieui committed
234 235 236 237 238 239
            return Completion(
                the_input.auto_completion,
                sorted(self.core.pending_invites.keys()),
                1,
                '',
                quotify=True)
240

241
    def bind(self, the_input):
242 243
        n = the_input.get_argument_position()
        if n == 1:
mathieui's avatar
mathieui committed
244 245 246
            args = [
                key for key in self.core.key_func if not key.startswith('_')
            ]
247
        elif n == 2:
248
            args = [key for key in self.core.key_func]
mathieui's avatar
mathieui committed
249
        else:
250
            return False
251

252
        return Completion(the_input.new_completion, args, n, '', quotify=False)
253

254
    def message(self, the_input):
255 256 257
        """Completion for /message"""
        n = the_input.get_argument_position(quoted=True)
        if n >= 2:
258
            return False
259 260 261 262 263 264 265 266 267
        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)
268

269
    def invite(self, the_input):
270 271 272
        """Completion for /invite"""
        n = the_input.get_argument_position(quoted=True)
        if n == 1:
mathieui's avatar
mathieui committed
273 274 275
            comp = reduce(lambda x, y: x + [i.jid for i in y],
                          (roster[jid].resources for jid in roster.jids()
                           if len(roster[jid])), [])
276
            comp = sorted(comp)
mathieui's avatar
mathieui committed
277 278 279
            bares = sorted(roster[contact].bare_jid
                           for contact in roster.jids()
                           if len(roster[contact]))
280 281
            off = sorted(jid for jid in roster.jids() if jid not in bares)
            comp = comp + bares + off
282
            return Completion(the_input.new_completion, comp, n, quotify=True)
283 284
        elif n == 2:
            rooms = []
285
            for tab in self.core.get_tabs(tabs.MucTab):
286 287 288
                if tab.joined:
                    rooms.append(tab.name)
            rooms.sort()
mathieui's avatar
mathieui committed
289 290
            return Completion(
                the_input.new_completion, rooms, n, '', quotify=True)
291

Maxime Buquet's avatar
Maxime Buquet committed
292 293 294 295 296 297 298 299 300 301 302 303 304
    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)

305
    def activity(self, the_input):
306 307 308 309
        """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
310 311 312 313 314
            return Completion(
                the_input.new_completion,
                sorted(pep.ACTIVITIES.keys()),
                n,
                quotify=True)
315 316 317 318 319
        elif n == 2:
            if args[1] in pep.ACTIVITIES:
                l = list(pep.ACTIVITIES[args[1]])
                l.remove('category')
                l.sort()
320
                return Completion(the_input.new_completion, l, n, quotify=True)
321

322
    def mood(self, the_input):
323 324 325
        """Completion for /mood"""
        n = the_input.get_argument_position(quoted=True)
        if n == 1:
mathieui's avatar
mathieui committed
326 327 328 329 330
            return Completion(
                the_input.new_completion,
                sorted(pep.MOODS.keys()),
                1,
                quotify=True)
331

332
    def last_activity(self, the_input):
333 334 335 336 337
        """
        Completion for /last_activity <jid>
        """
        n = the_input.get_argument_position(quoted=False)
        if n >= 2:
338
            return False
mathieui's avatar
mathieui committed
339 340 341 342 343
        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)
344

345
    def server_cycle(self, the_input):
346 347
        """Completion for /server_cycle"""
        serv_list = set()
348
        for tab in self.core.get_tabs(tabs.MucTab):
349 350
            serv = safeJID(tab.name).server
            serv_list.add(serv)
351
        return Completion(the_input.new_completion, sorted(serv_list), 1, ' ')
352

353
    def set(self, the_input):
354 355 356 357 358 359 360 361
        """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]
362
                if plugin_name not in self.core.plugin_manager.plugins:
mathieui's avatar
mathieui committed
363 364
                    return Completion(
                        the_input.new_completion, [], n, quotify=True)
365
                plugin = self.core.plugin_manager.plugins[plugin_name]
mathieui's avatar
mathieui committed
366 367 368 369
                end_list = [
                    '%s|%s' % (plugin_name, section)
                    for section in plugin.config.sections()
                ]
mathieui's avatar
mathieui committed
370
            else:
371 372 373 374 375 376 377
                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]
378
                if plugin_name not in self.core.plugin_manager.plugins:
mathieui's avatar
mathieui committed
379 380
                    return Completion(
                        the_input.new_completion, [''], n, quotify=True)
381
                plugin = self.core.plugin_manager.plugins[plugin_name]
382 383
                end_list = set(plugin.config.options(section or plugin_name))
                if plugin.config.default:
mathieui's avatar
mathieui committed
384 385
                    end_list.update(
                        plugin.config.default.get(section or plugin_name, {}))
386 387 388 389 390 391 392 393 394 395 396 397 398
                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]
399
                if plugin_name not in self.core.plugin_manager.plugins:
mathieui's avatar
mathieui committed
400 401
                    return Completion(
                        the_input.new_completion, [''], n, quotify=True)
402
                plugin = self.core.plugin_manager.plugins[plugin_name]
mathieui's avatar
mathieui committed
403 404 405 406 407
                end_list = [
                    str(
                        plugin.config.get(args[2], '', section
                                          or plugin_name)), ''
                ]
408 409 410 411 412
            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
413
        else:
414
            return False
415
        return Completion(the_input.new_completion, end_list, n, quotify=True)
416

417
    def set_default(self, the_input):
418 419 420 421 422 423 424
        """ 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])):
425
            return Completion(self.set, the_input)
426
        return False
427

428
    def toggle(self, the_input):
429
        "Completion for /toggle"
mathieui's avatar
mathieui committed
430 431 432 433 434
        return Completion(
            the_input.new_completion,
            config.options('Poezio'),
            1,
            quotify=False)
435

436
    def bookmark_local(self, the_input):
437 438 439 440 441
        """Completion for /bookmark_local"""
        n = the_input.get_argument_position(quoted=True)
        args = common.shell_split(the_input.text)

        if n >= 2:
442
            return False
443 444 445 446 447
        if len(args) == 1:
            args.append('')
        jid = safeJID(args[1])

        if jid.server and (jid.resource or jid.full.endswith('/')):
448
            tab = self.core.tabs.by_name_and_class(jid.bare, tabs.MucTab)
449
            nicks = [tab.own_nick] if tab else []
mathieui's avatar
mathieui committed
450 451
            default = os.environ.get('USER') if os.environ.get(
                'USER') else 'poezio'
452 453
            nick = config.get('default_nick')
            if not nick:
454
                if default not in nicks:
455 456
                    nicks.append(default)
            else:
457
                if nick not in nicks:
458 459
                    nicks.append(nick)
            jids_list = ['%s/%s' % (jid.bare, nick) for nick in nicks]
mathieui's avatar
mathieui committed
460 461
            return Completion(
                the_input.new_completion, jids_list, 1, quotify=True)
462
        muc_list = [tab.name for tab in self.core.get_tabs(tabs.MucTab)]
463
        muc_list.append('*')
464
        return Completion(the_input.new_completion, muc_list, 1, quotify=True)