openMSX
Completer.cc
Go to the documentation of this file.
1 #include "Completer.hh"
2 #include "InterpreterOutput.hh"
3 #include "FileContext.hh"
4 #include "FileOperations.hh"
5 #include "ReadDir.hh"
6 #include "utf8_unchecked.hh"
7 #include "stringsp.hh"
8 #include "xrange.hh"
9 #include <algorithm>
10 
11 using std::vector;
12 using std::string;
13 
14 namespace openmsx {
15 
16 InterpreterOutput* Completer::output = nullptr;
17 
18 
20  : name(name_.str())
21 {
22 }
23 
25 {
26 }
27 
28 const string& Completer::getName() const
29 {
30  return name;
31 }
32 
33 static bool formatHelper(const vector<string_ref>& input, size_t columnLimit,
34  vector<string>& result)
35 {
36  size_t column = 0;
37  auto it = input.begin();
38  do {
39  size_t maxcolumn = column;
40  for (size_t i = 0; (i < result.size()) && (it != input.end());
41  ++i, ++it) {
42  auto curSize = utf8::unchecked::size(result[i]);
43  result[i] += string(column - curSize, ' ');
44  result[i] += it->str();
45  maxcolumn = std::max(maxcolumn,
46  utf8::unchecked::size(result[i]));
47  if (maxcolumn > columnLimit) return false;
48  }
49  column = maxcolumn + 2;
50  } while (it != input.end());
51  return true;
52 }
53 
54 static vector<string> format(const vector<string_ref>& input, size_t columnLimit)
55 {
56  vector<string> result;
57  for (size_t lines = 1; lines < input.size(); ++lines) {
58  result.assign(lines, string());
59  if (formatHelper(input, columnLimit, result)) {
60  return result;
61  }
62  }
63  for (auto& s : input) {
64  result.push_back(s.str());
65  }
66  return result;
67 }
68 
69 bool Completer::equalHead(string_ref s1, string_ref s2, bool caseSensitive)
70 {
71  if (s2.size() < s1.size()) return false;
72  if (caseSensitive) {
73  return memcmp(s1.data(), s2.data(), s1.size()) == 0;
74  } else {
75  return strncasecmp(s1.data(), s2.data(), s1.size()) == 0;
76  }
77 }
78 
79 bool Completer::completeImpl(string& str, vector<string_ref> matches,
80  bool caseSensitive)
81 {
82  for (auto& m : matches) {
83  assert(equalHead(str, m, caseSensitive)); (void)m;
84  }
85 
86  if (matches.empty()) {
87  // no matching values
88  return false;
89  }
90  if (matches.size() == 1) {
91  // only one match
92  str = matches.front().str();
93  return true;
94  }
95 
96  // Sort and remove duplicates.
97  // For efficiency it's best if the list doesn't contain duplicates to
98  // start with. Though sometimes this is hard to avoid. E.g. when doing
99  // filename completion + some extra allowed strings and one of these
100  // extra strings is the same as one of the filenames.
101  sort(matches.begin(), matches.end());
102  matches.erase(unique(matches.begin(), matches.end()), matches.end());
103 
104  bool expanded = false;
105  while (true) {
106  auto it = matches.begin();
107  if (str.size() == it->size()) {
108  // match is as long as first word
109  goto out; // TODO rewrite this
110  }
111  // expand with one char and check all strings
112  auto begin = it->begin();
113  auto end = begin + str.size();
114  utf8::unchecked::next(end); // one more utf8 char
115  string_ref string2(begin, end);
116  for (; it != matches.end(); ++it) {
117  if (!equalHead(string2, *it, caseSensitive)) {
118  goto out; // TODO rewrite this
119  }
120  }
121  // no conflict found
122  str = string2.str();
123  expanded = true;
124  }
125  out:
126  if (!expanded && output) {
127  // print all possibilities
128  for (auto& line : format(matches, output->getOutputColumns() - 1)) {
129  output->output(line);
130  }
131  }
132  return false;
133 }
134 
135 void Completer::completeFileName(vector<string>& tokens,
136  const FileContext& context)
137 {
138  completeFileNameImpl(tokens, context, vector<string_ref>());
139 }
140 
141 void Completer::completeFileNameImpl(vector<string>& tokens,
142  const FileContext& context,
143  vector<string_ref> matches)
144 {
145  string& filename = tokens.back();
146  filename = FileOperations::expandTilde(filename);
147  filename = FileOperations::expandCurrentDirFromDrive(filename);
148  string_ref basename = FileOperations::getBaseName(filename);
149 
150  vector<string> paths;
151  if (FileOperations::isAbsolutePath(filename)) {
152  paths.push_back("");
153  } else {
154  paths = context.getPaths();
155  }
156 
157  vector<string> filenames;
158  for (auto& p : paths) {
159  string dirname = FileOperations::join(p, basename);
160  ReadDir dir(FileOperations::getNativePath(dirname));
161  while (dirent* de = dir.getEntry()) {
162  string name = FileOperations::join(dirname, de->d_name);
163  if (FileOperations::exists(name)) {
164  string nm = FileOperations::join(basename, de->d_name);
165  if (FileOperations::isDirectory(name)) {
166  nm += '/';
167  }
169  if (equalHead(filename, nm, true)) {
170  filenames.push_back(nm);
171  }
172  }
173  }
174  }
175  for (auto& f : filenames) {
176  matches.push_back(f);
177  }
178  bool t = completeImpl(filename, matches, true);
179  if (t && !filename.empty() && (filename.back() != '/')) {
180  // completed filename, start new token
181  tokens.push_back("");
182  }
183 }
184 
186 {
187  output = output_;
188 }
189 
190 } // namespace openmsx