// // Completer.cc for pekwm // Copyright © 2009 Claes Nästén // // This program is licensed under the GNU GPL. // See the LICENSE file for more information. // #ifdef HAVE_CONFIG_H #include "config.h" #endif // HAVE_CONFIG_H #include #include #include #include #include #include extern "C" { #include #include #include } #include "Completer.hh" #include "Config.hh" #include "Util.hh" #include "PWinObj.hh" #include "MenuHandler.hh" using std::copy; using std::cerr; using std::wcerr; using std::endl; using std::list; using std::vector; using std::string; using std::wstring; using std::pair; ActionCompleterMethod::StateMatch ActionCompleterMethod::STATE_MATCHES[] = { StateMatch(ActionCompleterMethod::STATE_STATE, L"set"), StateMatch(ActionCompleterMethod::STATE_STATE, L"unset"), StateMatch(ActionCompleterMethod::STATE_STATE, L"toggle"), StateMatch(ActionCompleterMethod::STATE_MENU, L"showmenu") }; /** * Find matches for word in completions_list and add to completions. */ unsigned int CompleterMethod::complete_word(completions_list &completions_list, complete_list &completions, const std::wstring &word) { unsigned int completed = 0, equality = -1; completions_it it(completions_list.begin()); for (; it != completions_list.end(); ++it) { if (it->first.size() < word.size()) { continue; } equality = it->first.compare(0, word.size(), word, 0, word.size()); if (equality == 0) { completions.push_back(it->second); completed++; } } return completed; } /** * Complete str with available path elements. */ unsigned int PathCompleterMethod::complete(CompletionState &completion_state) { return complete_word(_path_list, completion_state.completions, completion_state.word); } /** * Refresh available completions. */ void PathCompleterMethod::refresh(void) { // Clear out previous data _path_list.clear(); vector path_parts; Util::splitString(getenv("PATH") ? getenv("PATH") : "", path_parts, ":"); vector::iterator it(path_parts.begin()); for (; it != path_parts.end(); ++it) { DIR *dh = opendir(it->c_str()); if (dh) { refresh_path(dh, Util::to_wide_str(*it)); closedir(dh); } } _path_list.unique(); _path_list.sort(); } /** * Refresh single directory. */ void PathCompleterMethod::refresh_path(DIR *dh, const std::wstring path) { struct dirent *entry; while ((entry = readdir(dh)) != 0) { if (entry->d_name[0] == '.') { continue; } wstring name(Util::to_wide_str(entry->d_name)); _path_list.push_back(pair(name, name)); _path_list.push_back(pair(path + L"/" + name, path + L"/" + name)); } } /** * Check if str matches state prefix. */ bool ActionCompleterMethod::StateMatch::is_state(const wstring &str, size_t pos) { if (str.size() - pos < _prefix_len) { return false; } else { return str.compare(pos, _prefix_len, _prefix, _prefix_len) == 0; } } /** * Complete action. */ unsigned int ActionCompleterMethod::complete(CompletionState &state) { State type_state = find_state(state); switch (type_state) { case STATE_STATE: return complete_word(_state_list, state.completions, state.word_lower); case STATE_MENU: return complete_word(_menu_list, state.completions, state.word_lower); case STATE_ACTION: return complete_word(_action_list, state.completions, state.word_lower); case STATE_NO: default: return 0; } } /** * Build list of completions from available actions. */ void ActionCompleterMethod::refresh(void) { completions_list_from_name_list(Config::instance()->getActionNameList(), _action_list); completions_list_from_name_list(Config::instance()->getStateNameList(), _state_list); completions_list_from_name_list(MenuHandler::instance()->getMenuNames(), _menu_list); } /** * Build completions_list from a list with strings. */ void ActionCompleterMethod::completions_list_from_name_list(std::list name_list, completions_list &completions_list) { completions_list.clear(); list::iterator it(name_list.begin()); for (; it != name_list.end(); ++it) { wstring name(Util::to_wide_str(*it)); wstring name_lower(name); Util::to_lower(name_lower); completions_list.push_back(pair(name_lower, name)); } completions_list.unique(); completions_list.sort(); } /** * Detect state being completed */ ActionCompleterMethod::State ActionCompleterMethod::find_state(CompletionState &completion_state) { State state = STATE_ACTION; if (completion_state.word_begin != 0) { state = find_state_match(completion_state.part_lower, completion_state.part_begin); } return state; } /** * Find matching state. */ ActionCompleterMethod::State ActionCompleterMethod::find_state_match(const std::wstring &str, size_t pos) { for (int i = 0; i < STATE_NUM; ++i) { if (STATE_MATCHES[i].is_state(str, pos)) { return STATE_MATCHES[i].get_state(); } } return STATE_NO; } /** * Completer destructor, free up resources used by methods. */ Completer::~Completer(void) { list::iterator it(_methods.begin()); for (; it != _methods.end(); ++it) { delete *it; } } /** * Find completions for string with the cursor at position. */ complete_list Completer::find_completions(const wstring &str, unsigned int pos) { // Get current part of str, if it is empty return no completions. CompletionState state; state.part = state.part_lower = get_part(str, pos, state.part_begin, state.part_end); if (! state.part.size()) { return state.completions; } // Get word at position, the one that will be completed state.word = state.word_lower = get_word_at_position(str, pos, state.word_begin, state.word_end); Util::to_lower(state.part_lower); Util::to_lower(state.word_lower); // Go through completer methods and add completions. list::iterator it(_methods.begin()); for (; it != _methods.end(); ++it) { if ((*it)->can_complete(state.part)) { (*it)->complete(state); } } return state.completions; } /** * Perform actual completion, returns new string with completion * inserted if any. * * @param str String to complete. * @param pos Cursor position in string. * @return Completed string. */ wstring Completer::do_complete(const wstring &str, unsigned int &pos, complete_list &completions, complete_it &it) { // Do not perform completion if there is nothing to complete if (! completions.size()) { pos = str.size(); return str; } // Wrap completions, return original string if (it == completions.end()) { it = completions.begin(); pos = str.size(); return str; } // Get current word, this is the one being replaced size_t word_begin, word_end; wstring word(get_word_at_position(str, pos, word_begin, word_end)); // Replace the current word wstring completed(str); completed.replace(word_begin, word_end - word_begin, *it); // Update position pos = word_begin + it->size(); // Increment completion it++; return completed; } /** * Find current part being completed, string can be split up with * separators and only one part should be treated at a time. * * @param str String to find part in. * @param pos Position in string. * @param part_begin * @param part_end * @return Current part of string. */ wstring Completer::get_part(const wstring &str, unsigned int pos, size_t &part_begin, size_t &part_end) { // If no separators are defined, do nothing. if (! _separators.size()) { return str; } // Get beginning and end of string, add 1 for removal of separator part_begin = String::safe_position(str.find_last_of(_separators, pos), 0, 1); part_end = String::safe_position(str.find_first_of(_separators, pos), str.size()); // Strip spaces from the beginning of the string part_begin = String::safe_position(str.find_first_not_of(L" \t", part_begin), part_end); return str.substr(part_begin, part_end - part_begin); } /** * Get word at position. */ wstring Completer::get_word_at_position(const wstring &str, unsigned int pos, size_t &word_begin, size_t &word_end) { // Get beginning and end of string, add 1 for removal of separator word_begin = String::safe_position(str.find_last_of(L" \t", pos), 0, 1); word_end = String::safe_position(str.find_first_of(L" \t", pos), str.size()); return str.substr(word_begin, word_end - word_begin); }