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