improve fbrun completion

- streamline code
- indicate completion by making use of selection
- fix buggy behavior (notably subsequent completions and FS path
  following)
- support "~" in paths
- support chunk completion
  (ie. "mp[layer] ~/vid[eos/favporn.mp4]" can be completed in both
   tokens; buggy with paths including spaces in non-leafs)

REQUEST: 223
This commit is contained in:
Thomas Lübking 2016-07-23 00:30:25 +02:00
parent ed75e883db
commit d741b6fe6e
2 changed files with 127 additions and 129 deletions

View file

@ -65,8 +65,10 @@ FbRun::FbRun(int x, int y, size_t width):
m_gc(*this),
m_end(false),
m_current_history_item(0),
m_last_completion_prefix(""),
m_current_apps_item(0),
m_current_files_item(-1),
m_last_completion_path(""),
m_current_apps_item(-1),
m_completion_pos(std::string::npos),
m_cursor(XCreateFontCursor(FbTk::App::instance()->display(), XC_xterm)) {
setGC(m_gc.gc());
@ -255,7 +257,7 @@ void FbRun::keyPressEvent(XKeyEvent &ke) {
break;
case XK_Tab:
did_tab_complete = true;
tabCompleteHistory();
tabComplete(m_history, m_current_history_item, true); // reverse
break;
}
} else if ((ke.state & (Mod1Mask|ShiftMask)) == (Mod1Mask | ShiftMask)) {
@ -293,9 +295,9 @@ void FbRun::keyPressEvent(XKeyEvent &ke) {
break;
}
}
clear();
if (!did_tab_complete)
m_last_completion_prefix = "";
m_completion_pos = std::string::npos;
clear();
}
void FbRun::lockPosition(bool size_too) {
@ -357,134 +359,126 @@ void FbRun::lastHistoryItem() {
}
}
void FbRun::tabCompleteHistory() {
if (m_current_history_item == 0 || m_history.empty() ) {
void FbRun::tabComplete(const std::vector<std::string> &list, int &currentItem, bool reverse) {
if (list.empty()) {
XBell(m_display, 0);
} else {
unsigned int nr= 0;
unsigned int history_item = m_current_history_item - 1;
if (m_last_completion_prefix.empty())
m_last_completion_prefix = text().substr(0, textStartPos() + cursorPosition());
while (history_item != m_current_history_item && nr++ < m_history.size()) {
if (m_history[history_item].find(m_last_completion_prefix) == 0) {
m_current_history_item = history_item;
setText(FbTk::BiDiString(m_history[m_current_history_item]));
cursorEnd();
break;
}
if (history_item == 0) // loop
history_item = m_history.size();
history_item--;
}
if (history_item == m_current_history_item) XBell(m_display, 0);
return;
}
if (m_completion_pos == std::string::npos)
m_completion_pos = textStartPos() + cursorPosition();
size_t split = text().find_last_of(' ', m_completion_pos);
if (split == std::string::npos)
split = 0;
else
++split; // skip space
std::string prefix = text().substr(split, m_completion_pos - split);
if (currentItem < 0)
currentItem = 0;
else if (currentItem >= list.size())
currentItem = list.size() - 1;
int item = currentItem;
while (true) {
if (reverse) {
if (--item < 0)
item = list.size() - 1;
} else {
if (++item >= list.size())
item = 0;
}
if (list.at(item).find(prefix) == 0) {
setText(FbTk::BiDiString(text().substr(0, split) + list.at(item)));
if (item == currentItem) {
cursorEnd();
m_completion_pos = std::string::npos;
} else {
select(split + prefix.size(), text().size() - (prefix.size() + split));
}
currentItem = item;
return;
}
if (item == currentItem) {
cursorEnd();
m_completion_pos = std::string::npos;
return;
}
}
// found nothing
XBell(m_display, 0);
}
void FbRun::tabCompleteApps() {
static bool first_run= true;
if (m_last_completion_prefix.empty())
m_last_completion_prefix = text().substr(0, textStartPos() + cursorPosition());
string prefix = m_last_completion_prefix;
FbTk::Directory dir;
if (m_completion_pos == std::string::npos)
m_completion_pos = textStartPos() + cursorPosition();
size_t split = text().find_last_of(' ', m_completion_pos);
if (split == std::string::npos)
split = 0;
else
++split; // skip the space
std::string prefix = text().substr(split, m_completion_pos - split);
if (prefix.empty()) {
XBell(m_display, 0);
return;
}
if (prefix.at(0) == '/' || prefix.at(0) == '.' || prefix.at(0) == '~') {
// we're completing a directory, find subdirs
split = prefix.find_last_of('/');
prefix = prefix.substr(0, split+1);
if (prefix != m_last_completion_path) {
m_files.clear();
m_current_files_item = -1;
m_last_completion_path = prefix;
bool add_dirs= false;
bool changed_prefix= false;
// (re)build m_apps-container
if (first_run || m_last_completion_prefix != prefix) {
first_run= false;
string path;
if(!prefix.empty() &&
string("/.~").find_first_of(prefix[0]) != string::npos) {
size_t rseparator= prefix.find_last_of("/");
path= prefix.substr(0, rseparator + 1) + ":";
add_dirs= true;
} else {
char* tmp_path = getenv("PATH");
if (tmp_path)
path = tmp_path;
FbTk::Directory dir;
std::string path = prefix;
if (path.at(0) == '~')
path.replace(0,1,getenv("HOME"));
dir.open(path.c_str());
int n = dir.entries();
while (--n > -1) {
std::string entry = dir.readFilename();
if (entry == "." || entry == "..")
continue;
if (FbTk::FileUtil::isDirectory(std::string(path + entry).c_str()))
m_files.push_back(prefix + entry + "/");
else
m_files.push_back(prefix + entry);
}
dir.close();
sort(m_files.begin(), m_files.end());
}
m_apps.clear();
unsigned int l;
unsigned int r;
for(l= 0, r= 0; r < path.size(); r++) {
if ((path[r]==':' || r == path.size() - 1) && r - l > 0) {
string filename;
string fncomplete;
dir.open(path.substr(l, r - l).c_str());
int n= dir.entries();
if (n >= 0) {
while(n--) {
filename= dir.readFilename();
fncomplete= dir.name() +
(*dir.name().rbegin() != '/' ? "/" : "") +
filename;
// directories in dirmode ?
if (add_dirs && FbTk::FileUtil::isDirectory(fncomplete.c_str()) &&
filename != ".." && filename != ".") {
m_apps.push_back(fncomplete);
// executables in dirmode ?
} else if (add_dirs && FbTk::FileUtil::isRegularFile(fncomplete.c_str()) &&
FbTk::FileUtil::isExecutable(fncomplete.c_str()) &&
(prefix == "" ||
fncomplete.substr(0, prefix.size()) == prefix)) {
m_apps.push_back(fncomplete);
// executables in $PATH ?
} else if (FbTk::FileUtil::isRegularFile(fncomplete.c_str()) &&
FbTk::FileUtil::isExecutable(fncomplete.c_str()) &&
(prefix == "" ||
filename.substr(0, prefix.size()) == prefix)) {
m_apps.push_back(filename);
tabComplete(m_files, m_current_files_item);
} else {
static bool first_run = true;
if (first_run) {
first_run = false;
std::string path = getenv("PATH");
FbTk::Directory dir;
for (unsigned int l = 0, r = 0; r < path.size(); ++r) {
if ((path.at(r) == ':' || r == path.size() - 1) && r - l > 1) {
dir.open(path.substr(l, r - l).c_str());
prefix = dir.name() + (*dir.name().rbegin() == '/' ? "" : "/");
int n = dir.entries();
while (--n > -1) {
std::string entry = dir.readFilename();
std::string file = prefix + entry;
if (FbTk::FileUtil::isExecutable(file.c_str()) &&
!FbTk::FileUtil::isDirectory(file.c_str())) {
m_apps.push_back(entry);
}
}
dir.close();
l = r + 1;
}
l= r + 1;
dir.close();
}
sort(m_apps.begin(), m_apps.end());
unique(m_apps.begin(), m_apps.end());
}
sort(m_apps.begin(), m_apps.end());
unique(m_apps.begin(), m_apps.end());
m_last_completion_prefix = prefix;
changed_prefix= true;
m_current_apps_item= 0;
}
if (m_apps.empty() ) {
XBell(m_display, 0);
} else {
size_t apps_item = m_current_apps_item + (changed_prefix ? 0 : 1);
bool loop= false;
while (true) {
if (apps_item >= m_apps.size() ) {
loop = true;
apps_item = 0;
}
if ((!changed_prefix || loop) && apps_item == m_current_apps_item) {
break;
}
if (m_apps[apps_item].find(prefix) == 0) {
m_current_apps_item = apps_item;
if (add_dirs && FbTk::FileUtil::isDirectory(m_apps[m_current_apps_item].c_str()))
setText(m_apps[m_current_apps_item] + "/");
else
setText(m_apps[m_current_apps_item]);
cursorEnd();
break;
}
apps_item++;
}
if (!changed_prefix && apps_item == m_current_apps_item)
XBell(m_display, 0);
tabComplete(m_apps, m_current_apps_item);
}
}

View file

@ -80,7 +80,7 @@ private:
void adjustEndPos();
void firstHistoryItem();
void lastHistoryItem();
void tabCompleteHistory();
void tabComplete(const std::vector<std::string> &list, int &current, bool reverse = false);
void tabCompleteApps();
bool m_print; ///< the input should be printed to stdout rather than run
@ -89,16 +89,20 @@ private:
int m_bevel;
FbTk::GContext m_gc; ///< graphic context
bool m_end; ///< marks when this object is done
std::vector<std::string> m_history; ///< history list of commands
std::string m_history_file; ///< holds filename for command history file
size_t m_current_history_item; ///< holds current position in command history
std::string m_last_completion_prefix; ///< last prefix we completed on
typedef std::vector<std::string> AppsContainer;
typedef AppsContainer::iterator AppsContainerIt;
AppsContainer m_apps; ///< holds all apps in $PATH
size_t m_current_apps_item; ///< holds current position in apps-history
int m_current_history_item; ///< holds current position in command history
std::vector<std::string> m_files;
int m_current_files_item;
std::string m_last_completion_path; ///< last prefix we completed on
std::vector<std::string> m_apps;
int m_current_apps_item; ///< holds current position in apps-history
size_t m_completion_pos;
Cursor m_cursor;
FbTk::FbPixmap m_pixmap;