diff options
author | Thomas Lübking <thomas.luebking@gmail.com> | 2016-07-22 22:30:25 (GMT) |
---|---|---|
committer | Thomas Lübking <thomas.luebking@gmail.com> | 2016-07-23 14:58:04 (GMT) |
commit | d741b6fe6e805b570bb899e777ea7101f6395721 (patch) | |
tree | 97a1d8f2b8098da18263ee62cd5362701ee926ff | |
parent | ed75e883db8426e889a46ce61b9f4bd1a4d6bc66 (diff) | |
download | fluxbox-d741b6fe6e805b570bb899e777ea7101f6395721.zip fluxbox-d741b6fe6e805b570bb899e777ea7101f6395721.tar.bz2 |
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
-rw-r--r-- | util/fbrun/FbRun.cc | 230 | ||||
-rw-r--r-- | util/fbrun/FbRun.hh | 22 |
2 files changed, 125 insertions, 127 deletions
diff --git a/util/fbrun/FbRun.cc b/util/fbrun/FbRun.cc index 8cc6b16..7fde936 100644 --- a/util/fbrun/FbRun.cc +++ b/util/fbrun/FbRun.cc | |||
@@ -65,8 +65,10 @@ FbRun::FbRun(int x, int y, size_t width): | |||
65 | m_gc(*this), | 65 | m_gc(*this), |
66 | m_end(false), | 66 | m_end(false), |
67 | m_current_history_item(0), | 67 | m_current_history_item(0), |
68 | m_last_completion_prefix(""), | 68 | m_current_files_item(-1), |
69 | m_current_apps_item(0), | 69 | m_last_completion_path(""), |
70 | m_current_apps_item(-1), | ||
71 | m_completion_pos(std::string::npos), | ||
70 | m_cursor(XCreateFontCursor(FbTk::App::instance()->display(), XC_xterm)) { | 72 | m_cursor(XCreateFontCursor(FbTk::App::instance()->display(), XC_xterm)) { |
71 | 73 | ||
72 | setGC(m_gc.gc()); | 74 | setGC(m_gc.gc()); |
@@ -255,7 +257,7 @@ void FbRun::keyPressEvent(XKeyEvent &ke) { | |||
255 | break; | 257 | break; |
256 | case XK_Tab: | 258 | case XK_Tab: |
257 | did_tab_complete = true; | 259 | did_tab_complete = true; |
258 | tabCompleteHistory(); | 260 | tabComplete(m_history, m_current_history_item, true); // reverse |
259 | break; | 261 | break; |
260 | } | 262 | } |
261 | } else if ((ke.state & (Mod1Mask|ShiftMask)) == (Mod1Mask | ShiftMask)) { | 263 | } else if ((ke.state & (Mod1Mask|ShiftMask)) == (Mod1Mask | ShiftMask)) { |
@@ -293,9 +295,9 @@ void FbRun::keyPressEvent(XKeyEvent &ke) { | |||
293 | break; | 295 | break; |
294 | } | 296 | } |
295 | } | 297 | } |
296 | clear(); | ||
297 | if (!did_tab_complete) | 298 | if (!did_tab_complete) |
298 | m_last_completion_prefix = ""; | 299 | m_completion_pos = std::string::npos; |
300 | clear(); | ||
299 | } | 301 | } |
300 | 302 | ||
301 | void FbRun::lockPosition(bool size_too) { | 303 | void FbRun::lockPosition(bool size_too) { |
@@ -357,134 +359,126 @@ void FbRun::lastHistoryItem() { | |||
357 | } | 359 | } |
358 | } | 360 | } |
359 | 361 | ||
360 | void FbRun::tabCompleteHistory() { | 362 | void FbRun::tabComplete(const std::vector<std::string> &list, int ¤tItem, bool reverse) { |
361 | if (m_current_history_item == 0 || m_history.empty() ) { | 363 | if (list.empty()) { |
362 | XBell(m_display, 0); | 364 | XBell(m_display, 0); |
363 | } else { | 365 | return; |
364 | unsigned int nr= 0; | 366 | } |
365 | unsigned int history_item = m_current_history_item - 1; | 367 | |
366 | if (m_last_completion_prefix.empty()) | 368 | if (m_completion_pos == std::string::npos) |
367 | m_last_completion_prefix = text().substr(0, textStartPos() + cursorPosition()); | 369 | m_completion_pos = textStartPos() + cursorPosition(); |
368 | 370 | size_t split = text().find_last_of(' ', m_completion_pos); | |
369 | while (history_item != m_current_history_item && nr++ < m_history.size()) { | 371 | if (split == std::string::npos) |
370 | if (m_history[history_item].find(m_last_completion_prefix) == 0) { | 372 | split = 0; |
371 | m_current_history_item = history_item; | 373 | else |
372 | setText(FbTk::BiDiString(m_history[m_current_history_item])); | 374 | ++split; // skip space |
375 | std::string prefix = text().substr(split, m_completion_pos - split); | ||
376 | |||
377 | if (currentItem < 0) | ||
378 | currentItem = 0; | ||
379 | else if (currentItem >= list.size()) | ||
380 | currentItem = list.size() - 1; | ||
381 | int item = currentItem; | ||
382 | |||
383 | while (true) { | ||
384 | if (reverse) { | ||
385 | if (--item < 0) | ||
386 | item = list.size() - 1; | ||
387 | } else { | ||
388 | if (++item >= list.size()) | ||
389 | item = 0; | ||
390 | } | ||
391 | if (list.at(item).find(prefix) == 0) { | ||
392 | setText(FbTk::BiDiString(text().substr(0, split) + list.at(item))); | ||
393 | if (item == currentItem) { | ||
373 | cursorEnd(); | 394 | cursorEnd(); |
374 | break; | 395 | m_completion_pos = std::string::npos; |
396 | } else { | ||
397 | select(split + prefix.size(), text().size() - (prefix.size() + split)); | ||
375 | } | 398 | } |
376 | if (history_item == 0) // loop | 399 | currentItem = item; |
377 | history_item = m_history.size(); | 400 | return; |
378 | history_item--; | 401 | } |
402 | if (item == currentItem) { | ||
403 | cursorEnd(); | ||
404 | m_completion_pos = std::string::npos; | ||
405 | return; | ||
379 | } | 406 | } |
380 | if (history_item == m_current_history_item) XBell(m_display, 0); | ||
381 | } | 407 | } |
408 | // found nothing | ||
409 | XBell(m_display, 0); | ||
382 | } | 410 | } |
383 | 411 | ||
384 | void FbRun::tabCompleteApps() { | ||
385 | |||
386 | static bool first_run= true; | ||
387 | if (m_last_completion_prefix.empty()) | ||
388 | m_last_completion_prefix = text().substr(0, textStartPos() + cursorPosition()); | ||
389 | string prefix = m_last_completion_prefix; | ||
390 | FbTk::Directory dir; | ||
391 | |||
392 | bool add_dirs= false; | ||
393 | bool changed_prefix= false; | ||
394 | |||
395 | // (re)build m_apps-container | ||
396 | if (first_run || m_last_completion_prefix != prefix) { | ||
397 | first_run= false; | ||
398 | 412 | ||
399 | string path; | 413 | void FbRun::tabCompleteApps() { |
400 | 414 | ||
401 | if(!prefix.empty() && | 415 | if (m_completion_pos == std::string::npos) |
402 | string("/.~").find_first_of(prefix[0]) != string::npos) { | 416 | m_completion_pos = textStartPos() + cursorPosition(); |
403 | size_t rseparator= prefix.find_last_of("/"); | 417 | size_t split = text().find_last_of(' ', m_completion_pos); |
404 | path= prefix.substr(0, rseparator + 1) + ":"; | 418 | if (split == std::string::npos) |
405 | add_dirs= true; | 419 | split = 0; |
406 | } else { | 420 | else |
407 | char* tmp_path = getenv("PATH"); | 421 | ++split; // skip the space |
408 | if (tmp_path) | 422 | std::string prefix = text().substr(split, m_completion_pos - split); |
409 | path = tmp_path; | 423 | if (prefix.empty()) { |
424 | XBell(m_display, 0); | ||
425 | return; | ||
426 | } | ||
427 | if (prefix.at(0) == '/' || prefix.at(0) == '.' || prefix.at(0) == '~') { | ||
428 | // we're completing a directory, find subdirs | ||
429 | split = prefix.find_last_of('/'); | ||
430 | prefix = prefix.substr(0, split+1); | ||
431 | if (prefix != m_last_completion_path) { | ||
432 | m_files.clear(); | ||
433 | m_current_files_item = -1; | ||
434 | m_last_completion_path = prefix; | ||
435 | |||
436 | FbTk::Directory dir; | ||
437 | std::string path = prefix; | ||
438 | if (path.at(0) == '~') | ||
439 | path.replace(0,1,getenv("HOME")); | ||
440 | dir.open(path.c_str()); | ||
441 | int n = dir.entries(); | ||
442 | while (--n > -1) { | ||
443 | std::string entry = dir.readFilename(); | ||
444 | if (entry == "." || entry == "..") | ||
445 | continue; | ||
446 | if (FbTk::FileUtil::isDirectory(std::string(path + entry).c_str())) | ||
447 | m_files.push_back(prefix + entry + "/"); | ||
448 | else | ||
449 | m_files.push_back(prefix + entry); | ||
450 | } | ||
451 | dir.close(); | ||
452 | sort(m_files.begin(), m_files.end()); | ||
410 | } | 453 | } |
411 | m_apps.clear(); | 454 | tabComplete(m_files, m_current_files_item); |
412 | 455 | } else { | |
413 | unsigned int l; | 456 | static bool first_run = true; |
414 | unsigned int r; | 457 | if (first_run) { |
415 | 458 | first_run = false; | |
416 | for(l= 0, r= 0; r < path.size(); r++) { | 459 | std::string path = getenv("PATH"); |
417 | if ((path[r]==':' || r == path.size() - 1) && r - l > 0) { | 460 | FbTk::Directory dir; |
418 | string filename; | 461 | for (unsigned int l = 0, r = 0; r < path.size(); ++r) { |
419 | string fncomplete; | 462 | if ((path.at(r) == ':' || r == path.size() - 1) && r - l > 1) { |
420 | dir.open(path.substr(l, r - l).c_str()); | 463 | dir.open(path.substr(l, r - l).c_str()); |
421 | int n= dir.entries(); | 464 | prefix = dir.name() + (*dir.name().rbegin() == '/' ? "" : "/"); |
422 | if (n >= 0) { | 465 | int n = dir.entries(); |
423 | while(n--) { | 466 | while (--n > -1) { |
424 | filename= dir.readFilename(); | 467 | std::string entry = dir.readFilename(); |
425 | fncomplete= dir.name() + | 468 | std::string file = prefix + entry; |
426 | (*dir.name().rbegin() != '/' ? "/" : "") + | 469 | if (FbTk::FileUtil::isExecutable(file.c_str()) && |
427 | filename; | 470 | !FbTk::FileUtil::isDirectory(file.c_str())) { |
428 | 471 | m_apps.push_back(entry); | |
429 | // directories in dirmode ? | ||
430 | if (add_dirs && FbTk::FileUtil::isDirectory(fncomplete.c_str()) && | ||
431 | filename != ".." && filename != ".") { | ||
432 | m_apps.push_back(fncomplete); | ||
433 | // executables in dirmode ? | ||
434 | } else if (add_dirs && FbTk::FileUtil::isRegularFile(fncomplete.c_str()) && | ||
435 | FbTk::FileUtil::isExecutable(fncomplete.c_str()) && | ||
436 | (prefix == "" || | ||
437 | fncomplete.substr(0, prefix.size()) == prefix)) { | ||
438 | m_apps.push_back(fncomplete); | ||
439 | // executables in $PATH ? | ||
440 | } else if (FbTk::FileUtil::isRegularFile(fncomplete.c_str()) && | ||
441 | FbTk::FileUtil::isExecutable(fncomplete.c_str()) && | ||
442 | (prefix == "" || | ||
443 | filename.substr(0, prefix.size()) == prefix)) { | ||
444 | m_apps.push_back(filename); | ||
445 | } | 472 | } |
446 | } | 473 | } |
474 | dir.close(); | ||
475 | l = r + 1; | ||
447 | } | 476 | } |
448 | l= r + 1; | ||
449 | dir.close(); | ||
450 | } | ||
451 | } | ||
452 | sort(m_apps.begin(), m_apps.end()); | ||
453 | unique(m_apps.begin(), m_apps.end()); | ||
454 | |||
455 | m_last_completion_prefix = prefix; | ||
456 | changed_prefix= true; | ||
457 | m_current_apps_item= 0; | ||
458 | } | ||
459 | |||
460 | if (m_apps.empty() ) { | ||
461 | XBell(m_display, 0); | ||
462 | } else { | ||
463 | size_t apps_item = m_current_apps_item + (changed_prefix ? 0 : 1); | ||
464 | bool loop= false; | ||
465 | |||
466 | while (true) { | ||
467 | if (apps_item >= m_apps.size() ) { | ||
468 | loop = true; | ||
469 | apps_item = 0; | ||
470 | } | ||
471 | |||
472 | if ((!changed_prefix || loop) && apps_item == m_current_apps_item) { | ||
473 | break; | ||
474 | } | ||
475 | if (m_apps[apps_item].find(prefix) == 0) { | ||
476 | m_current_apps_item = apps_item; | ||
477 | if (add_dirs && FbTk::FileUtil::isDirectory(m_apps[m_current_apps_item].c_str())) | ||
478 | setText(m_apps[m_current_apps_item] + "/"); | ||
479 | else | ||
480 | setText(m_apps[m_current_apps_item]); | ||
481 | cursorEnd(); | ||
482 | break; | ||
483 | } | 477 | } |
484 | apps_item++; | 478 | sort(m_apps.begin(), m_apps.end()); |
479 | unique(m_apps.begin(), m_apps.end()); | ||
485 | } | 480 | } |
486 | if (!changed_prefix && apps_item == m_current_apps_item) | 481 | tabComplete(m_apps, m_current_apps_item); |
487 | XBell(m_display, 0); | ||
488 | } | 482 | } |
489 | } | 483 | } |
490 | 484 | ||
diff --git a/util/fbrun/FbRun.hh b/util/fbrun/FbRun.hh index 5d678b0..a74d347 100644 --- a/util/fbrun/FbRun.hh +++ b/util/fbrun/FbRun.hh | |||
@@ -80,7 +80,7 @@ private: | |||
80 | void adjustEndPos(); | 80 | void adjustEndPos(); |
81 | void firstHistoryItem(); | 81 | void firstHistoryItem(); |
82 | void lastHistoryItem(); | 82 | void lastHistoryItem(); |
83 | void tabCompleteHistory(); | 83 | void tabComplete(const std::vector<std::string> &list, int ¤t, bool reverse = false); |
84 | void tabCompleteApps(); | 84 | void tabCompleteApps(); |
85 | 85 | ||
86 | bool m_print; ///< the input should be printed to stdout rather than run | 86 | bool m_print; ///< the input should be printed to stdout rather than run |
@@ -89,16 +89,20 @@ private: | |||
89 | int m_bevel; | 89 | int m_bevel; |
90 | FbTk::GContext m_gc; ///< graphic context | 90 | FbTk::GContext m_gc; ///< graphic context |
91 | bool m_end; ///< marks when this object is done | 91 | bool m_end; ///< marks when this object is done |
92 | |||
92 | std::vector<std::string> m_history; ///< history list of commands | 93 | std::vector<std::string> m_history; ///< history list of commands |
93 | std::string m_history_file; ///< holds filename for command history file | 94 | std::string m_history_file; ///< holds filename for command history file |
94 | size_t m_current_history_item; ///< holds current position in command history | 95 | int m_current_history_item; ///< holds current position in command history |
95 | std::string m_last_completion_prefix; ///< last prefix we completed on | 96 | |
96 | 97 | std::vector<std::string> m_files; | |
97 | typedef std::vector<std::string> AppsContainer; | 98 | int m_current_files_item; |
98 | typedef AppsContainer::iterator AppsContainerIt; | 99 | std::string m_last_completion_path; ///< last prefix we completed on |
99 | AppsContainer m_apps; ///< holds all apps in $PATH | 100 | |
100 | size_t m_current_apps_item; ///< holds current position in apps-history | 101 | std::vector<std::string> m_apps; |
101 | 102 | int m_current_apps_item; ///< holds current position in apps-history | |
103 | |||
104 | size_t m_completion_pos; | ||
105 | |||
102 | Cursor m_cursor; | 106 | Cursor m_cursor; |
103 | 107 | ||
104 | FbTk::FbPixmap m_pixmap; | 108 | FbTk::FbPixmap m_pixmap; |