aboutsummaryrefslogtreecommitdiff
path: root/main/src/ui/chat_input/occupants_tab_completer.vala
blob: ab1b75e0a7a84203e20961690c85e6ff4fabc7cb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
using Gdk;
using Gee;
using Gtk;

using Dino.Entities;
using Xmpp;

namespace Dino.Ui {

/**
 * - With given prefix: Complete from occupant list (sorted lexicographically)
 * - W/o prefix: Complete from received messages (most recent first)
 * - At the start (with ",") and in the middle of a text
 * - Backwards tabbing
 */
public class OccupantsTabCompletor {

    private StreamInteractor stream_interactor;
    private Conversation? conversation;
    private TextView text_input;

    private Gee.List<string> completions = new ArrayList<string>();
    private bool active = false;
    private int index = -1;

    public OccupantsTabCompletor(StreamInteractor stream_interactor, TextView text_input) {
        this.stream_interactor = stream_interactor;
        this.text_input = text_input;

        text_input.key_press_event.connect(on_text_input_key_press);
    }

    public void initialize_for_conversation(Conversation conversation) {
        this.conversation = conversation;
    }

    public bool on_text_input_key_press(EventKey event) {
        if (conversation.type_ == Conversation.Type.GROUPCHAT) {
            if (event.keyval == Key.Tab || event.keyval == Key.ISO_Left_Tab) {
                string text = text_input.buffer.text;
                int start_index = int.max(text.last_index_of(" "), text.last_index_of("\n")) + 1;
                string word = text.substring(start_index);
                if (!active) {
                    if (word == "") {
                        completions = generate_completions_from_messages();
                    } else {
                        completions = generate_completions_from_occupants(word);
                    }
                    if (completions.size > 0) {
                        active = true;
                        index = -1;
                    }
                }
                if (event.keyval != Key.ISO_Group_Shift && active) {
                    text_input.buffer.text = next_completion(event.keyval == Key.ISO_Left_Tab);
                    return true;
                }
            } else if (event.keyval != Key.Shift_L && active) {
                active = false;
            }
        }
        return false;
    }

    private string next_completion(bool backwards) {
        string text = text_input.buffer.text;
        int start_index = int.max(text.last_index_of(" "), text.last_index_of("\n")) + 1;
        string prev_completion = text.substring(start_index);
        if (index > -1) {
            start_index = int.max(
                text.last_index_of(completions[index]),
                text.substring(0, text.length - 1).last_index_of("\n")
            );
            prev_completion = text.substring(start_index);
        }
        if (backwards) {
            index = int.max(index, 0) - 1;
            if (index < 0) index = completions.size - 1;
        } else {
            index = (index + 1) % (completions.size);
        }
        if (start_index == 0) {
            return completions[index] + ", ";
        } else {
            return text.substring(0, text.length - prev_completion.length) + completions[index] + " ";
        }
    }

    private Gee.List<string> generate_completions_from_messages() {
        Gee.List<string> ret = new ArrayList<string>();
        Gee.List<Message> messages = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages(conversation, 10);
        for (int i = messages.size - 1; i > 0; i--) {
            string resourcepart = messages[i].from.resourcepart;
            Jid? own_nick = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account);
            if (resourcepart != null && resourcepart != "" && (own_nick == null || resourcepart != own_nick.resourcepart) && !ret.contains(resourcepart)) {
                ret.add(resourcepart);
            }
        }
        return ret;
    }

    private Gee.List<string> generate_completions_from_occupants(string prefix) {
        Gee.List<string> ret = new ArrayList<string>();

        // First suggest nicks that have recently writen something
        Gee.List<Message> messages = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages(conversation, 50);
        for (int i = messages.size - 1; i > 0; i--) {
            string resourcepart = messages[i].from.resourcepart;
            Jid? own_nick = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account);
            if (resourcepart != null && resourcepart != "" &&
                    resourcepart.to_string().down().has_prefix(prefix.down()) &&
                    (own_nick == null || resourcepart != own_nick.resourcepart) &&
                    !ret.contains(resourcepart)) {
                ret.add(resourcepart.to_string());
            }
        }

        // Then, suggest other nicks in alphabetical order
        Gee.List<Jid>? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_other_occupants(conversation.counterpart, conversation.account);
        Gee.List<string> filtered_occupants = new ArrayList<string>();
        if (occupants != null) {
            foreach (Jid jid in occupants) {
                string resourcepart = jid.resourcepart.to_string();
                if (resourcepart.down().has_prefix(prefix.down()) && !ret.contains(resourcepart)) {
                    filtered_occupants.add(resourcepart);
                }
            }
        }
        filtered_occupants.sort();

        ret.add_all(filtered_occupants);
        return ret;
    }
}

}