1 /* bzflag
2  * Copyright (c) 1993-2021 Tim Riker
3  *
4  * This package is free software;  you can redistribute it and/or
5  * modify it under the terms of the license found in the file
6  * named COPYING that should have accompanied this file.
7  *
8  * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
9  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
10  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11  */
12 
13 #include "common.h"
14 
15 // implementation header
16 #include "AutoCompleter.h"
17 
18 // system headers
19 #include <ctype.h>
20 #include <string.h>
21 #include <algorithm>
22 
23 
WordRecord(const std::string & w,bool q)24 AutoCompleter::WordRecord::WordRecord(const std::string& w, bool q)
25 {
26     word = w;
27     quoteString = q;
28 }
29 
operator <(const WordRecord & w) const30 bool AutoCompleter::WordRecord::operator<(const WordRecord& w) const
31 {
32     return (word < w.word);
33 }
34 
operator ==(const WordRecord & w) const35 bool AutoCompleter::WordRecord::operator==(const WordRecord& w) const
36 {
37     return (word == w.word);
38 }
39 
operator !=(const WordRecord & w) const40 bool AutoCompleter::WordRecord::operator!=(const WordRecord& w) const
41 {
42     return (word != w.word);
43 }
44 
45 
registerWord(const std::string & str,bool quoteString)46 void AutoCompleter::registerWord(const std::string& str, bool quoteString)
47 {
48     // only use 'quoteString' if it applies
49     if (quoteString)
50     {
51         quoteString = false;
52         for (int i = 0; i < (int)str.size(); i++)
53         {
54             if (isspace(str[i]))
55             {
56                 quoteString = true;
57                 break;
58             }
59         }
60     }
61     WordRecord rec(str, quoteString);
62     words.insert(std::lower_bound(words.begin(), words.end(), rec), rec);
63 }
64 
65 
unregisterWord(const std::string & str)66 void AutoCompleter::unregisterWord(const std::string& str)
67 {
68     WordRecord rec(str, false);
69     while (true)
70     {
71         std::vector<WordRecord>::iterator iter =
72             std::lower_bound(words.begin(), words.end(), rec);
73         if (iter != words.end() && *iter == rec)
74             words.erase(iter);
75         else
76             return;
77     }
78 }
79 
80 
complete(const std::string & str,std::string * matches)81 std::string AutoCompleter::complete(const std::string& str, std::string* matches)
82 {
83     if (str.size() == 0)
84         return str;
85 
86     // from the last space
87     const int lastSpace = str.find_last_of(" \t");
88     const std::string tail = str.substr(lastSpace + 1);
89     if (tail.size() == 0)
90         return str;
91     const std::string head = str.substr(0, lastSpace + 1);
92 
93     // find the first and last word with the prefix str
94     std::vector<WordRecord>::iterator first, last;
95     WordRecord rec(tail, false);
96     first = std::lower_bound(words.begin(), words.end(), rec);
97     if ((first == words.end()) ||
98             (first->word.substr(0, tail.size()) != tail))
99     {
100         return str; // no match
101     }
102     std::string tmp = tail;
103     tmp[tmp.size() - 1]++;
104     last = std::lower_bound(first, words.end(), WordRecord(tmp, false)) - 1;
105 
106     // get a list of partial matches
107     if (matches != NULL)
108     {
109         *matches = "";
110         if (first != last)
111         {
112             std::vector<WordRecord>::iterator it = first;
113             for (it = first; it != (last + 1); ++it)
114             {
115                 std::string tmp2 = it->word;
116                 // strip the trailing whitespace
117                 while ((tmp2.size() > 0) && isspace(tmp2[tmp2.size() - 1]))
118                     tmp2.resize(tmp2.size() - 1);
119                 if (tmp2.size() > 0)
120                 {
121                     if (it->quoteString)
122                         *matches += "\"" + tmp2 + "\" ";
123                     else
124                         *matches += tmp2 + " ";
125                 }
126             }
127         }
128     }
129 
130     // FIXME: hack to allow the auto-completion to work with old /clientquery
131     const char* hackCmd = "/clientquery";
132     const unsigned int hackLen = strlen(hackCmd);
133     const bool hack = (strncasecmp(head.c_str(), hackCmd, hackLen) == 0);
134 
135     const bool noQuotes = (lastSpace == -1) || hack;
136 
137     // return the largest common prefix without any spaces
138     const int minLen = first->word.size() < last->word.size() ?
139                        first->word.size() : last->word.size();
140     int i;
141     for (i = 0; i < minLen; ++i)
142     {
143         if ((!noQuotes && isspace(first->word[i])) ||
144                 (first->word[i] != last->word[i]))
145             break;
146     }
147 
148     if (!noQuotes && first->quoteString && (first == last))
149     {
150         const std::string quoted = "\"" + first->word + "\"";
151         return (head + quoted);
152     }
153     else
154         return (head + first->word.substr(0, i));
155 }
156 
157 
158 
DefaultCompleter()159 DefaultCompleter::DefaultCompleter()
160 {
161     setDefaults();
162 }
163 
setDefaults()164 void DefaultCompleter::setDefaults()
165 {
166     words.clear();
167     registerWord("/ban ");
168     registerWord("/banlist");
169     registerWord("/checkip ");
170     registerWord("/countdown");
171     registerWord("/clientquery");
172     registerWord("/date");
173     registerWord("/dumpvars");
174     registerWord("/flag ");
175     registerWord("reset");
176     registerWord("up");
177     registerWord("show");
178     registerWord("/flaghistory");
179     registerWord("/gameover");
180     registerWord("/grouplist");
181     registerWord("/groupperms");
182     registerWord("/handicap");
183     registerWord("/help");
184     registerWord("/highlight ");
185     registerWord("/hostban ");
186     registerWord("/hostunban ");
187     registerWord("/hostbanlist");
188     registerWord("/idban ");
189     registerWord("/idunban ");
190     registerWord("/idbanlist");
191     registerWord("/idlist");
192     registerWord("/idlestats");
193     registerWord("/idletime");
194     registerWord("/jitterdrop");
195     registerWord("/jitterwarn");
196     registerWord("/kick ");
197     registerWord("/kill ");
198     registerWord("/lagdrop");
199     registerWord("/lagstats");
200     registerWord("/lagwarn ");
201     registerWord("/localset ");
202     registerWord("/modcount ");
203     registerWord("/mute ");
204     registerWord("/owner");
205     registerWord("/packetlossdrop");
206     registerWord("/packetlosswarn");
207     registerWord("/password ");
208     registerWord("/playerlist");
209     registerWord("/poll ");
210     registerWord("ban");
211     registerWord("kick");
212     registerWord("kill");
213     registerWord("/quit");
214     registerWord("/record");
215     registerWord("start");
216     registerWord("stop");
217     registerWord("size");
218     registerWord("rate");
219     registerWord("stats");
220     registerWord("file");
221     registerWord("save");
222     registerWord("/reload");
223     registerWord("/masterban"); // also uses list
224     registerWord("reload");
225     registerWord("flush");
226     registerWord("/removegroup ");
227     registerWord("/replay ");
228     registerWord("list");
229     registerWord("load");
230     registerWord("play");
231     registerWord("skip");
232     registerWord("/report ");
233     registerWord("/reset");
234     registerWord("/retexture");
235     registerWord("/roampos ");
236     registerWord("/saveworld ");
237     registerWord("/say ");
238     registerWord("/sendhelp ");
239     registerWord("/serverdebug");
240     registerWord("/serverquery");
241     registerWord("/set");
242     registerWord("/setgroup ");
243     registerWord("/showgroup ");
244     registerWord("/showperms ");
245     registerWord("/shutdownserver");
246     registerWord("/silence ");
247     registerWord("/unsilence ");
248     registerWord("/superkill");
249     registerWord("/time");
250     registerWord("/unban ");
251     registerWord("/unmute ");
252     registerWord("/uptime");
253     registerWord("/veto");
254     registerWord("/viewreports");
255     registerWord("/vote");
256     registerWord("/loadplugin");
257     registerWord("/listplugins");
258     registerWord("/unloadplugin");
259 }
260 
261 // Local Variables: ***
262 // mode: C++ ***
263 // tab-width: 4 ***
264 // c-basic-offset: 4 ***
265 // indent-tabs-mode: nil ***
266 // End: ***
267 // ex: shiftwidth=4 tabstop=4
268