From 489af9787c89c1ee390deb39b280fcc7df727f93 Mon Sep 17 00:00:00 2001
From: fluxgen <fluxgen>
Date: Tue, 24 Jun 2003 10:22:42 +0000
Subject: emacs keybindings and tab completion, thanks David J Burger

---
 util/fbrun/FbRun.cc | 279 +++++++++++++++++++++++++++++++++++++++++-----------
 util/fbrun/FbRun.hh |  26 ++++-
 2 files changed, 242 insertions(+), 63 deletions(-)

diff --git a/util/fbrun/FbRun.cc b/util/fbrun/FbRun.cc
index 992255f..334414b 100644
--- a/util/fbrun/FbRun.cc
+++ b/util/fbrun/FbRun.cc
@@ -1,5 +1,5 @@
-// FbRun.hh
-// Copyright (c) 2002 Henrik Kinnunen (fluxgen@linuxmail.org)
+// FbRun.cc
+// Copyright (c) 2002-2003 Henrik Kinnunen (fluxgen<at>users.sourceforge.net)
 //
 // Permission is hereby granted, free of charge, to any person obtaining a
 // copy of this software and associated documentation files (the "Software"),
@@ -19,7 +19,7 @@
 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 // DEALINGS IN THE SOFTWARE.
 
-// $Id: FbRun.cc,v 1.11 2003/04/27 02:26:21 rathnor Exp $
+// $Id: FbRun.cc,v 1.12 2003/06/24 10:22:42 fluxgen Exp $
 
 #include "FbRun.hh"
 
@@ -50,13 +50,17 @@ FbRun::FbRun(int x, int y, size_t width):
     m_end(false),
     m_current_history_item(0),
     m_cursor(XCreateFontCursor(FbTk::App::instance()->display(), XC_xterm)),
-    m_cursor_pos(0) {
-    XDefineCursor(FbTk::App::instance()->display(), m_win.window(), m_cursor);
+    m_start_pos(0),
+    m_end_pos(0),
+    m_cursor_pos(0)
+{
+    m_win.setCursor(m_cursor);
     // setting nomaximize in local resize
     resize(width, m_font.height());
     FbTk::EventManager::instance()->registerEventHandler(*this, m_win.window());
 }
 
+
 FbRun::~FbRun() {
     hide();
     FbTk::EventManager::instance()->unregisterEventHandler(m_win.window());
@@ -71,7 +75,7 @@ void FbRun::run(const std::string &command) {
     }
 
     hide(); // hide gui
-	
+    
     // save command history to file
     if (m_runtext.size() != 0) { // no need to save empty command
         // open file in append mode
@@ -129,7 +133,6 @@ void FbRun::setBackground(const FbTk::Color &color) {
     redrawLabel();
 }
 
-
 void FbRun::setText(const string &text) {
     m_runtext = text;
     redrawLabel();
@@ -144,7 +147,7 @@ void FbRun::move(int x, int y) {
 }
 
 void FbRun::resize(size_t width, size_t height) {
-    m_win.resize(width, height);	
+    m_win.resize(width, height);    
     setNoMaximize();
 }
 
@@ -167,49 +170,79 @@ void FbRun::drawString(int x, int y,
                        const char *text, size_t len) {
     assert(m_gc);
 
-    // check right boundary and adjust text drawing
-    size_t text_width = m_font.textWidth(text, len);
-    size_t startpos = 0;
-    if (text_width > m_win.width()) {
-        for (; startpos < len; ++startpos) {
-            if (m_font.textWidth(text+startpos, len-startpos) < m_win.width())
-                break;
-        }		
-    }
-
-    m_font.drawText(m_win.window(), DefaultScreen(m_display), m_gc, text + startpos, len-startpos, x, y - 2);
-    int cursor_pos = m_font.textWidth(text + m_cursor_pos, len - startpos) + 1;
+    m_font.drawText(m_win.window(), DefaultScreen(m_display), m_gc, text + m_start_pos, m_end_pos - m_start_pos, x, y - 2);
     // draw cursor position
-    XDrawLine(FbTk::App::instance()->display(), m_win.window(), m_gc,
-              cursor_pos, 0,
-              cursor_pos, m_font.height());
+    int cursor_pos = m_font.textWidth(text + m_start_pos, m_cursor_pos) + 1;
+    m_win.drawLine(m_gc, cursor_pos, 0, cursor_pos, m_font.height());
 }
 
 void FbRun::keyPressEvent(XKeyEvent &ke) {
     KeySym ks;
     char keychar[1];
     XLookupString(&ke, keychar, 1, &ks, 0);
-    if (ks == XK_Escape) {
-        m_end = true;
-        hide();
-        FbTk::App::instance()->end(); // end program
-    } else if (ks == XK_Return) {
-        run(m_runtext);
-        m_runtext = ""; // clear text
-    } else if (ks == XK_BackSpace) {
-        if (m_runtext.size() != 0) { // we can't erase what we don't have ;)
-            m_runtext.erase(m_runtext.size()-1);
-            redrawLabel();
-            m_cursor_pos--;
+    // a modifier key by itself doesn't do anything
+    if (IsModifierKey(ks)) return;
+
+    if (ke.state) { // a modifier key is down
+        if (ke.state == ControlMask) {
+            switch (ks) {
+            case XK_b:
+                cursorLeft();
+                break;
+            case XK_f:
+                cursorRight();
+                break;
+            case XK_p:
+                prevHistoryItem();
+                break;
+            case XK_n:
+                nextHistoryItem();
+                break;
+            case XK_a:
+                cursorHome();
+                break;
+            case XK_e:
+                cursorEnd();
+                break;
+            case XK_d:
+                deleteForward();
+                break;
+            case XK_k:
+                killToEnd();
+                break;
+            }
+        } else if (ke.state == (Mod1Mask | ShiftMask)) {
+            switch (ks) {
+            case XK_less:
+                firstHistoryItem();
+                break;
+            case XK_greater:
+                lastHistoryItem();
+                break;
+            }
+        } else if (ke.state == ShiftMask) {
+            if (isprint(keychar[0]))insertCharacter(ks, keychar);
         }
-    } else if (! IsModifierKey(ks) && !IsCursorKey(ks)) { // insert normal character at cursor pos
-        char in_char[2] = {keychar[0], 0};
-        m_runtext.insert(m_cursor_pos, in_char); 
-        m_cursor_pos++;
-        redrawLabel(); 
-    } else if (IsCursorKey(ks)) {
-		
+    } else { // no modifier key
         switch (ks) {
+        case XK_Escape:
+            m_end = true;
+            hide();
+            FbTk::App::instance()->end(); // end program
+            break;
+        case XK_Return:
+            run(m_runtext);
+            m_runtext = ""; // clear text
+            break;
+        case XK_BackSpace:
+            backspace();
+            break;
+        case XK_Home:
+            cursorHome();
+            break;
+        case XK_End:
+            cursorEnd();
+            break;
         case XK_Up:
             prevHistoryItem();
             break;
@@ -222,18 +255,20 @@ void FbRun::keyPressEvent(XKeyEvent &ke) {
         case XK_Right:
             cursorRight();
             break;
+        case XK_Tab:
+            tabCompleteHistory();
+            break;
+        default:
+            if (isprint(keychar[0])) insertCharacter(ks, keychar);
         }
-        redrawLabel();
-    } else if (ks == XK_End) {
-        m_cursor_pos = m_runtext.size() - 1;
     }
+    redrawLabel();
 }
 
 void FbRun::exposeEvent(XExposeEvent &ev) {
     redrawLabel();
 }
 
-
 void FbRun::setNoMaximize() {
     // we don't need to maximize this window
     XSizeHints sh;
@@ -246,32 +281,160 @@ void FbRun::setNoMaximize() {
 }
 
 void FbRun::prevHistoryItem() {
-
-    if (m_current_history_item > 0 && m_history.size() > 0)
+    if (m_history.size() == 0 || m_current_history_item == 0) {
+        XBell(m_display, 0);
+    } else {
         m_current_history_item--;
-    if (m_current_history_item < m_history.size())
         m_runtext = m_history[m_current_history_item];
+        m_cursor_pos = m_end_pos = m_runtext.size();
+        adjustStartPos();
+    }
 }
 
 void FbRun::nextHistoryItem() {
-    m_current_history_item++;
-    if (m_current_history_item >= m_history.size()) {
-        m_current_history_item = m_history.size();
-        m_runtext = "";
-        return;
-    } else 
+    if (m_current_history_item == m_history.size()) {
+        XBell(m_display, 0);
+    } else {
+        m_current_history_item++;
+        if (m_current_history_item == m_history.size()) {
+            m_current_history_item = m_history.size();
+            m_runtext = "";
+            m_start_pos = m_cursor_pos = m_end_pos = 0;
+        } else {
+            m_runtext = m_history[m_current_history_item];
+            m_cursor_pos = m_end_pos = m_runtext.size();
+            adjustStartPos();
+        }
+    }
+}
+
+void FbRun::firstHistoryItem() {
+    if (m_history.size() == 0 || m_current_history_item == 0) {
+        XBell(m_display, 0);
+    } else {
+        m_current_history_item = 0;
         m_runtext = m_history[m_current_history_item];
+        m_cursor_pos = m_end_pos = m_runtext.size();
+        adjustStartPos();
+    }
+}
 
+void FbRun::lastHistoryItem() {
+    // actually one past the end
+    if (m_history.size() == 0) {
+        XBell(m_display, 0);
+    } else {
+        m_current_history_item = m_history.size();
+        m_runtext = "";
+        m_start_pos = m_cursor_pos = m_end_pos = 0;
+    }
 }
 
+void FbRun::tabCompleteHistory() {
+    if (m_current_history_item == 0) {
+        XBell(m_display, 0);
+    } else {
+        int history_item = m_current_history_item - 1;
+        string prefix = m_runtext.substr(0, m_cursor_pos);
+        while (history_item > - 1) {
+            if (m_history[history_item].find(prefix) == 0) {
+                m_current_history_item = history_item;
+                m_runtext = m_history[m_current_history_item];
+                adjustEndPos();
+                break;
+            }
+            history_item--;
+        }
+        if (history_item == -1) XBell(m_display, 0);
+    }
+}
 
 void FbRun::cursorLeft() {
-    if (m_cursor_pos > 0)
+    if (m_cursor_pos)
         m_cursor_pos--;
+    else if (m_start_pos) {
+        m_start_pos--;
+        adjustEndPos();
+    }
 }
 
 void FbRun::cursorRight() {
+    if (m_start_pos + m_cursor_pos < m_end_pos)
+        m_cursor_pos++;
+    else if (m_end_pos < m_runtext.size()) {
+        m_cursor_pos++;
+        m_end_pos++;
+        adjustStartPos();
+    }
+}
+
+void FbRun::cursorHome() {
+    m_start_pos = m_cursor_pos = 0;
+    adjustEndPos();
+}
+
+void FbRun::cursorEnd() {
+    m_cursor_pos = m_end_pos = m_runtext.size();
+    adjustStartPos();
+}
+
+void FbRun::backspace() {
+    if (m_start_pos || m_cursor_pos) {
+        m_runtext.erase(m_start_pos + m_cursor_pos - 1, 1);
+        if (m_cursor_pos)
+            m_cursor_pos--;
+        else
+            m_start_pos--;
+        adjustEndPos();
+    }
+}
+
+void FbRun::deleteForward() {
+    if (m_start_pos + m_cursor_pos < m_end_pos) {
+        m_runtext.erase(m_start_pos + m_cursor_pos, 1);
+        adjustEndPos();
+    }
+}
+
+void FbRun::killToEnd() {
+    if (m_start_pos + m_cursor_pos < m_end_pos) {
+        m_runtext.erase(m_start_pos + m_cursor_pos);
+        adjustEndPos();
+    }
+}
+
+void FbRun::insertCharacter(KeySym ks, char *keychar) {
+    char in_char[2] = {keychar[0], 0};
+    m_runtext.insert(m_start_pos + m_cursor_pos, in_char);
     m_cursor_pos++;
-    if (m_cursor_pos >= m_runtext.size())
-        m_cursor_pos = m_runtext.size() - 1;
+    m_end_pos++;
+    if (m_start_pos + m_cursor_pos < m_end_pos)
+        adjustEndPos();
+    else
+        adjustStartPos();
+}
+
+void FbRun::adjustEndPos() {
+    m_end_pos = m_runtext.size();
+    const char *text = m_runtext.c_str();
+    int text_width = m_font.textWidth(text + m_start_pos, m_end_pos - m_start_pos);
+    while (text_width > m_win.width()) {
+        m_end_pos--;
+        text_width = m_font.textWidth(text + m_start_pos, m_end_pos - m_start_pos);
+    }
+}
+
+void FbRun::adjustStartPos() {
+    const char *text = m_runtext.c_str();
+    int text_width = m_font.textWidth(text + m_start_pos, m_end_pos - m_start_pos);
+    if (text_width < m_win.width()) return;
+    int start_pos = 0;
+    text_width = m_font.textWidth(text + start_pos, m_end_pos - start_pos);
+    while (text_width > m_win.width()) {
+        start_pos++;
+        text_width = m_font.textWidth(text + start_pos, m_end_pos - start_pos);
+    }
+    // adjust m_cursor_pos according relative to change to m_start_pos
+    m_cursor_pos -= start_pos - m_start_pos;
+    m_start_pos = start_pos;
 }
diff --git a/util/fbrun/FbRun.hh b/util/fbrun/FbRun.hh
index 29a9afd..a92ca90 100644
--- a/util/fbrun/FbRun.hh
+++ b/util/fbrun/FbRun.hh
@@ -19,7 +19,7 @@
 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 // DEALINGS IN THE SOFTWARE.
 
-// $Id: FbRun.hh,v 1.9 2003/03/22 11:31:43 fluxgen Exp $
+// $Id: FbRun.hh,v 1.10 2003/06/24 10:22:42 fluxgen Exp $
 
 #ifndef FBRUN_HH
 #define FBRUN_HH
@@ -71,7 +71,7 @@ public:
     void exposeEvent(XExposeEvent &ev);
     void keyPressEvent(XKeyEvent &ev);
     ///@}
-	
+
 private:
     void nextHistoryItem();
     void prevHistoryItem();
@@ -79,11 +79,23 @@ private:
     void cursorRight();
     void drawString(int x, int y, const char *text, size_t len);
     void getSize(size_t &width, size_t &height);
-    void createWindow(int x, int y, size_t width, size_t height);	
-    void redrawLabel();	
+    void createWindow(int x, int y, size_t width, size_t height);
+    void redrawLabel();
     /// set no maximizable for this window
     void setNoMaximize();
 
+    void cursorHome();
+    void cursorEnd();
+    void backspace();
+    void deleteForward();
+    void killToEnd();
+    void insertCharacter(KeySym ks, char *keychar);
+    void adjustStartPos();
+    void adjustEndPos();
+    void firstHistoryItem();
+    void lastHistoryItem();
+    void tabCompleteHistory();
+
     FbTk::Font m_font; ///< font used to draw command text
     FbTk::FbWindow m_win;  ///< toplevel window 
     Display *m_display;  ///< display connection
@@ -95,7 +107,11 @@ private:
     size_t m_current_history_item; ///< holds current position in command history
     std::string m_history_file; ///< holds filename for command history file
     Cursor m_cursor;
-    int m_cursor_pos;
+
+    int m_start_pos; ///< start position of portion of text to display
+    int m_cursor_pos; ///< relative to m_start_pos
+    int m_end_pos; ///< end postition of portion of text to display
+
 };
 
 #endif // FBRUN_HH
-- 
cgit v0.11.2