#include "shell_log.h" #include namespace roro { void ShellLog::push(const std::string& line) { lines_.push_back(line); bytes_ += line.size() + 1; while (bytes_ > kMaxBytes && lines_.size() > 1) { bytes_ -= lines_.front().size() + 1; lines_.pop_front(); } revision_++; } void ShellLog::add(const std::string& line) { push(line); } void ShellLog::feed(const char* data, size_t len) { for (size_t i = 0; i < len; i++) { char c = data[i]; if (c == '\r') continue; if (c != '\n') { if (partial_.size() < 512) partial_ += c; // a line that never ends doesn't take the heap continue; } if (partial_.rfind("status: heap ", 0) != 0) push(partial_); partial_.clear(); } } void ShellLog::clear() { lines_.clear(); partial_.clear(); bytes_ = 0; revision_++; } namespace { bool isWord(const std::string& w) { if (w.empty()) return false; for (size_t i = 0; i < w.size(); i++) { bool small = w[i] >= 'a' && w[i] <= 'z', capital = w[i] >= 'A' && w[i] <= 'Z'; if (!(small || (i == 0 && capital))) return false; } return true; } std::vector split(const std::string& text, const std::string& by) { std::vector out; size_t at = 0; for (;;) { size_t next = text.find(by, at); out.push_back(text.substr(at, next == std::string::npos ? std::string::npos : next - at)); if (next == std::string::npos) return out; at = next + by.size(); } } // The words a command can have at `index`, given the `index` words before it: added to `out`. void nextWords(const std::string& command, const std::vector& before, size_t index, std::vector& out) { std::vector tokens; for (auto& t : split(command, " ")) if (!t.empty()) tokens.push_back(t); for (size_t i = 0; i < tokens.size(); i++) { std::vector either = split(tokens[i], "|"); // on|off: either of them bool words = true; for (auto& w : either) words = words && isWord(w); if (!words) return; // an , an [option], "...": the command's words end here if (i == index) { for (auto& w : either) if (std::find(out.begin(), out.end(), w) == out.end()) out.push_back(w); return; } if (std::find(either.begin(), either.end(), before[i]) == either.end()) return; // another command } } } // namespace std::string completeWords(const std::string& typed, const char* helpText, std::vector& matches) { matches.clear(); std::vector words = split(typed, " "); for (size_t i = 0; i + 1 < words.size(); i++) if (words[i].empty()) return typed; // two spaces: not ours to guess const std::string last = words.back(); words.pop_back(); if (words.empty() && last.empty()) return typed; std::vector next; for (auto& line : split(helpText ? helpText : "", "\n")) { std::string commands = line.substr(0, line.find(" ")); // the description starts at three spaces for (auto& command : split(commands, " | ")) nextWords(command, words, words.size(), next); } for (auto& w : next) if (w.rfind(last, 0) == 0) matches.push_back(w); if (matches.empty()) return typed; std::string common = matches[0]; for (auto& m : matches) { size_t n = 0; while (n < common.size() && n < m.size() && common[n] == m[n]) n++; common.resize(n); } std::string head = typed.substr(0, typed.size() - last.size()); if (matches.size() == 1) { matches.clear(); return head + common + " "; } return head + common; } namespace { char lower(char c) { return c >= 'A' && c <= 'Z' ? static_cast(c - 'A' + 'a') : c; } bool startsWithNoCase(const std::string& name, const std::string& prefix) { if (name.size() < prefix.size()) return false; for (size_t i = 0; i < prefix.size(); i++) if (lower(name[i]) != lower(prefix[i])) return false; return true; } bool takesAPath(const std::string& command) { static const char* const kCommands[] = {"ls", "du", "mkdir", "rm", "cp", "mv", "cat", "install"}; for (auto c : kCommands) if (command == c) return true; return false; } } // namespace bool splitForPath(const std::string& typed, PathToComplete& out) { size_t space = typed.rfind(' '); if (space == std::string::npos) return false; // still the command's own word std::string word = typed.substr(space + 1); if (!word.empty() && word[0] == '-') return false; // a switch if (word.empty() || word[0] != '/') { if (!takesAPath(typed.substr(0, typed.find(' ')))) return false; word = "/" + word; } size_t slash = word.rfind('/'); out.head = typed.substr(0, space + 1); out.folder = word.substr(0, slash + 1); out.prefix = word.substr(slash + 1); return true; } std::string completePath(const PathToComplete& what, const std::vector& names, std::vector& matches) { matches.clear(); for (auto& n : names) if (startsWithNoCase(n, what.prefix)) matches.push_back(n); if (matches.empty()) return what.head + what.folder + what.prefix; std::string common = matches[0]; for (auto& m : matches) { size_t n = 0; while (n < common.size() && n < m.size() && lower(common[n]) == lower(m[n])) n++; common.resize(n); } if (matches.size() == 1) { bool folder = !common.empty() && common.back() == '/'; matches.clear(); return what.head + what.folder + common + (folder ? "" : " "); } // Several: never shorter than what was typed (cases may differ past the prefix). if (common.size() < what.prefix.size()) common = what.prefix; return what.head + what.folder + common; } } // namespace roro