openMSX
Completer.cc
Go to the documentation of this file.
1 #include "Completer.hh"
2 #include "Interpreter.hh"
3 #include "InterpreterOutput.hh"
4 #include "FileContext.hh"
5 #include "FileOperations.hh"
6 #include "ReadDir.hh"
7 #include "ranges.hh"
8 #include "stl.hh"
9 #include "strCat.hh"
10 #include "stringsp.hh"
11 #include "TclObject.hh"
12 #include "utf8_unchecked.hh"
13 #include "view.hh"
14 
15 using std::vector;
16 using std::string;
17 
18 namespace openmsx {
19 
20 InterpreterOutput* Completer::output = nullptr;
21 
22 
24  : name(name_.str())
25 {
26 }
27 
28 static bool formatHelper(const vector<string_view>& input, size_t columnLimit,
29  vector<string>& result)
30 {
31  size_t column = 0;
32  auto it = begin(input);
33  do {
34  size_t maxcolumn = column;
35  for (size_t i = 0; (i < result.size()) && (it != end(input));
36  ++i, ++it) {
37  auto curSize = utf8::unchecked::size(result[i]);
38  strAppend(result[i], spaces(column - curSize), it->str());
39  maxcolumn = std::max(maxcolumn,
40  utf8::unchecked::size(result[i]));
41  if (maxcolumn > columnLimit) return false;
42  }
43  column = maxcolumn + 2;
44  } while (it != end(input));
45  return true;
46 }
47 
48 static vector<string> format(const vector<string_view>& input, size_t columnLimit)
49 {
50  vector<string> result;
51  for (size_t lines = 1; lines < input.size(); ++lines) {
52  result.assign(lines, string());
53  if (formatHelper(input, columnLimit, result)) {
54  return result;
55  }
56  }
57  append(result, view::transform(input, [](auto& s) { return s.str(); }));
58  return result;
59 }
60 
61 vector<string> Completer::formatListInColumns(const vector<string_view>& input)
62 {
63  return format(input, output->getOutputColumns() - 1);
64 }
65 
66 bool Completer::equalHead(string_view s1, string_view s2, bool caseSensitive)
67 {
68  if (s2.size() < s1.size()) return false;
69  if (caseSensitive) {
70  return memcmp(s1.data(), s2.data(), s1.size()) == 0;
71  } else {
72  return strncasecmp(s1.data(), s2.data(), s1.size()) == 0;
73  }
74 }
75 
76 bool Completer::completeImpl(string& str, vector<string_view> matches,
77  bool caseSensitive)
78 {
79  assert(ranges::all_of(matches, [&](auto& m) {
80  return equalHead(str, m, caseSensitive);
81  }));
82 
83  if (matches.empty()) {
84  // no matching values
85  return false;
86  }
87  if (matches.size() == 1) {
88  // only one match
89  str = matches.front().str();
90  return true;
91  }
92 
93  // Sort and remove duplicates.
94  // For efficiency it's best if the list doesn't contain duplicates to
95  // start with. Though sometimes this is hard to avoid. E.g. when doing
96  // filename completion + some extra allowed strings and one of these
97  // extra strings is the same as one of the filenames.
98  ranges::sort(matches);
99  matches.erase(ranges::unique(matches), end(matches));
100 
101  bool expanded = false;
102  while (true) {
103  auto it = begin(matches);
104  if (str.size() == it->size()) {
105  // match is as long as first word
106  goto out; // TODO rewrite this
107  }
108  // expand with one char and check all strings
109  auto b = begin(*it);
110  auto e = b + str.size();
111  utf8::unchecked::next(e); // one more utf8 char
112  string_view string2(b, e);
113  for (; it != end(matches); ++it) {
114  if (!equalHead(string2, *it, caseSensitive)) {
115  goto out; // TODO rewrite this
116  }
117  }
118  // no conflict found
119  str = string2.str();
120  expanded = true;
121  }
122  out:
123  if (!expanded && output) {
124  // print all possibilities
125  for (auto& line : formatListInColumns(matches)) {
126  output->output(line);
127  }
128  }
129  return false;
130 }
131 
132 void Completer::completeFileName(vector<string>& tokens,
133  const FileContext& context)
134 {
135  completeFileNameImpl(tokens, context, vector<string_view>());
136 }
137 
138 void Completer::completeFileNameImpl(vector<string>& tokens,
139  const FileContext& context,
140  vector<string_view> matches)
141 {
142  string& filename = tokens.back();
143  filename = FileOperations::expandTilde(filename);
144  filename = FileOperations::expandCurrentDirFromDrive(filename);
145  string_view dirname1 = FileOperations::getDirName(filename);
146 
147  vector<string> paths;
148  if (FileOperations::isAbsolutePath(filename)) {
149  paths.emplace_back();
150  } else {
151  paths = context.getPaths();
152  }
153 
154  vector<string> filenames;
155  for (auto& p : paths) {
156  string dirname = FileOperations::join(p, dirname1);
158  while (dirent* de = dir.getEntry()) {
159  string name = FileOperations::join(dirname, de->d_name);
160  if (FileOperations::exists(name)) {
161  string nm = FileOperations::join(dirname1, de->d_name);
162  if (FileOperations::isDirectory(name)) {
163  nm += '/';
164  }
166  if (equalHead(filename, nm, true)) {
167  filenames.push_back(nm);
168  }
169  }
170  }
171  }
172  append(matches, filenames);
173  bool t = completeImpl(filename, matches, true);
174  if (t && !filename.empty() && (filename.back() != '/')) {
175  // completed filename, start new token
176  tokens.emplace_back();
177  }
178 }
179 
180 void Completer::checkNumArgs(span<const TclObject> tokens, unsigned exactly, const char* errMessage) const
181 {
182  checkNumArgs(tokens, exactly, Prefix{exactly - 1}, errMessage);
183 }
184 
185 void Completer::checkNumArgs(span<const TclObject> tokens, AtLeast atLeast, const char* errMessage) const
186 {
187  checkNumArgs(tokens, atLeast, Prefix{atLeast.min - 1}, errMessage);
188 }
189 
190 void Completer::checkNumArgs(span<const TclObject> tokens, Between between, const char* errMessage) const
191 {
192  checkNumArgs(tokens, between, Prefix{between.min - 1}, errMessage);
193 }
194 
195 void Completer::checkNumArgs(span<const TclObject> tokens, unsigned exactly, Prefix prefix, const char* errMessage) const
196 {
197  if (tokens.size() == exactly) return;
198  getInterpreter().wrongNumArgs(prefix.n, tokens, errMessage);
199 }
200 
201 void Completer::checkNumArgs(span<const TclObject> tokens, AtLeast atLeast, Prefix prefix, const char* errMessage) const
202 {
203  if (tokens.size() >= atLeast.min) return;
204  getInterpreter().wrongNumArgs(prefix.n, tokens, errMessage);
205 }
206 
207 void Completer::checkNumArgs(span<const TclObject> tokens, Between between, Prefix prefix, const char* errMessage) const
208 {
209  if (tokens.size() >= between.min && tokens.size() <= between.max) return;
210  getInterpreter().wrongNumArgs(prefix.n, tokens, errMessage);
211 }
212 
213 } // namespace openmsx
const char * data() const
Definition: string_view.hh:57
auto transform(Range &&range, UnaryOp op)
Definition: view.hh:312
bool isAbsolutePath(string_view path)
Checks whether it&#39;s a absolute path or not.
string join(string_view part1, string_view part2)
Join two paths.
auto unique(ForwardRange &&range)
Definition: ranges.hh:137
Definition: span.hh:34
void checkNumArgs(span< const TclObject > tokens, unsigned exactly, const char *errMessage) const
Definition: Completer.cc:180
uint32_t next(octet_iterator &it)
vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:287
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:648
static void completeFileName(std::vector< std::string > &tokens, const FileContext &context, const RANGE &extra)
Definition: Completer.hh:138
auto begin(const string_view &x)
Definition: string_view.hh:151
Completer(string_view name)
Definition: Completer.cc:23
size_t size(string_view utf8)
virtual void output(string_view text)=0
void append(Result &)
Definition: stl.hh:354
string expandCurrentDirFromDrive(string_view path)
Get the current directory of the specified drive Linux: just return an empty string.
void wrongNumArgs(unsigned argc, span< const TclObject > tokens, const char *message)
Definition: Interpreter.cc:452
std::unique_ptr< Context > context
Definition: GLContext.cc:9
string getConventionalPath(string_view path)
Returns the path in conventional path-delimiter.
string getNativePath(string_view path)
Returns the path in native path-delimiter.
void sort(RandomAccessRange &&range)
Definition: ranges.hh:35
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
virtual unsigned getOutputColumns() const =0
bool exists(string_view filename)
Does this file (directory) exists?
This class implements a (close approximation) of the std::string_view class.
Definition: string_view.hh:16
std::vector< std::string > getPaths() const
Definition: FileContext.cc:103
bool isDirectory(const Stat &st)
std::string str() const
Definition: string_view.cc:12
bool all_of(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:119
string_view getDirName(string_view path)
Returns the directory portion of a path.
Simple wrapper around openmdir() / readdir() / closedir() functions.
Definition: ReadDir.hh:15
strCatImpl::ConcatSpaces spaces(size_t n)
Definition: strCat.hh:679
size_type size() const
Definition: string_view.hh:44
static std::vector< std::string > formatListInColumns(const std::vector< string_view > &input)
Definition: Completer.cc:61
virtual Interpreter & getInterpreter() const =0
struct dirent * getEntry()
Get directory entry for next file.
Definition: ReadDir.cc:17
string expandTilde(string_view path)
Expand the &#39;~&#39; character to the users home directory.
TclObject t
auto end(const string_view &x)
Definition: string_view.hh:152