<rdar://problem/14134716>

This is a rewrite of the command history facility of LLDB

It takes the history management out of the CommandInterpreter into its own CommandHistory class
It reimplements the command history command to allow more combinations of options to work correctly (e.g. com hist -c 1 -s 5)
It adds a new --wipe (-w) option to command history to allow clearing the history on demand
It extends the lldbtest runCmd: and expect: methods to allow adding commands to history if need be
It adds a test case for the reimplemented facility



git-svn-id: https://llvm.org/svn/llvm-project/lldb/trunk@184140 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/include/lldb/Interpreter/CommandHistory.h b/include/lldb/Interpreter/CommandHistory.h
new file mode 100644
index 0000000..5faaf1c
--- /dev/null
+++ b/include/lldb/Interpreter/CommandHistory.h
@@ -0,0 +1,76 @@
+//===-- CommandHistory.h ----------------------------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef liblldb_CommandHistory_h_
+#define liblldb_CommandHistory_h_
+
+// C Includes
+// C++ Includes
+#include <string>
+#include <vector>
+
+// Other libraries and framework includes
+// Project includes
+
+#include "lldb/lldb-private.h"
+#include "lldb/Core/Stream.h"
+#include "lldb/Host/Mutex.h"
+
+namespace lldb_private {
+    
+class CommandHistory
+{
+public:
+    CommandHistory ();
+    
+    ~CommandHistory ();
+    
+    size_t
+    GetSize () const;
+    
+    bool
+    IsEmpty () const;
+    
+    const char*
+    FindString (const char* input_str) const;
+    
+    const char*
+    GetStringAtIndex (size_t idx) const;
+    
+    const char*
+    operator [] (size_t idx) const;
+    
+    const char*
+    GetRecentmostString () const;
+    
+    void
+    AppendString (const std::string& str,
+                  bool reject_if_dupe = true);
+    
+    void
+    Clear ();
+
+    void
+    Dump (Stream& stream,
+          size_t start_idx = 0,
+          size_t stop_idx = UINT64_MAX) const;
+    
+    static const char g_repeat_char = '!';
+    
+private:
+    DISALLOW_COPY_AND_ASSIGN(CommandHistory);
+    
+    typedef std::vector<std::string> History;
+    mutable Mutex m_mutex;
+    History m_history;
+};
+
+} // namespace lldb_private
+
+#endif  // liblldb_CommandHistory_h_
diff --git a/include/lldb/Interpreter/CommandInterpreter.h b/include/lldb/Interpreter/CommandInterpreter.h
index 0621bf9..31fcc38 100644
--- a/include/lldb/Interpreter/CommandInterpreter.h
+++ b/include/lldb/Interpreter/CommandInterpreter.h
@@ -18,6 +18,7 @@
 #include "lldb/Core/Broadcaster.h"
 #include "lldb/Core/Debugger.h"
 #include "lldb/Core/Log.h"
+#include "lldb/Interpreter/CommandHistory.h"
 #include "lldb/Interpreter/CommandObject.h"
 #include "lldb/Interpreter/ScriptInterpreter.h"
 #include "lldb/Core/Event.h"
@@ -381,15 +382,6 @@
     bool
     GetSynchronous ();
     
-    void
-    DumpHistory (Stream &stream, uint32_t count) const;
-
-    void
-    DumpHistory (Stream &stream, uint32_t start, uint32_t end) const;
-    
-    const char *
-    FindHistoryString (const char *input_str) const;
-
     size_t
     FindLongestCommandWord (CommandObject::CommandMap &dict);
 
@@ -407,30 +399,42 @@
     SetBatchCommandMode (bool value) { m_batch_command_mode = value; }
     
     void
-    ChildrenTruncated()
+    ChildrenTruncated ()
     {
         if (m_truncation_warning == eNoTruncation)
             m_truncation_warning = eUnwarnedTruncation;
     }
     
     bool
-    TruncationWarningNecessary()
+    TruncationWarningNecessary ()
     {
         return (m_truncation_warning == eUnwarnedTruncation);
     }
     
     void
-    TruncationWarningGiven()
+    TruncationWarningGiven ()
     {
         m_truncation_warning = eWarnedTruncation;
     }
     
     const char *
-    TruncationWarningText()
+    TruncationWarningText ()
     {
         return "*** Some of your variables have more members than the debugger will show by default. To show all of them, you can either use the --show-all-children option to %s or raise the limit by changing the target.max-children-count setting.\n";
     }
     
+    const CommandHistory&
+    GetCommandHistory () const
+    {
+        return m_command_history;
+    }
+    
+    CommandHistory&
+    GetCommandHistory ()
+    {
+        return m_command_history;
+    }
+    
     //------------------------------------------------------------------
     // Properties
     //------------------------------------------------------------------
@@ -466,11 +470,10 @@
     CommandObject::CommandMap m_alias_dict;     // Stores user aliases/abbreviations for commands
     CommandObject::CommandMap m_user_dict;      // Stores user-defined commands
     OptionArgMap m_alias_options;               // Stores any options (with or without arguments) that go with any alias.
-    std::vector<std::string> m_command_history;
+    CommandHistory m_command_history;
     std::string m_repeat_command;               // Stores the command that will be executed for an empty command string.
     std::unique_ptr<ScriptInterpreter> m_script_interpreter_ap;
     char m_comment_char;
-    char m_repeat_char;
     bool m_batch_command_mode;
     ChildrenTruncatedWarningStatus m_truncation_warning;    // Whether we truncated children and whether the user has been told
     uint32_t m_command_source_depth;
diff --git a/include/lldb/Utility/Range.h b/include/lldb/Utility/Range.h
new file mode 100644
index 0000000..1257adb
--- /dev/null
+++ b/include/lldb/Utility/Range.h
@@ -0,0 +1,89 @@
+//===--------------------- Range.h ------------------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef utility_Range_h_
+#define utility_Range_h_
+
+#include <stdint.h>
+#include <algorithm>
+
+namespace lldb_utility {
+    
+class Range
+{
+public:
+    
+    typedef uint64_t ValueType;
+    
+    static const ValueType OPEN_END = UINT64_MAX;
+    
+    Range (const Range& rng);
+    
+    Range (ValueType low = 0,
+           ValueType high = OPEN_END);
+
+    Range&
+    operator = (const Range& rhs);
+    
+    ValueType
+    GetLow ()
+    {
+        return m_low;
+    }
+    
+    ValueType
+    GetHigh ()
+    {
+        return m_high;
+    }
+    
+    void
+    SetLow (ValueType low)
+    {
+        m_low = low;
+    }
+    
+    void
+    SetHigh (ValueType high)
+    {
+        m_high = high;
+    }
+    
+    void
+    Flip ();
+    
+    void
+    Intersection (const Range& other);
+    
+    void
+    Union (const Range& other);
+    
+    typedef bool (*RangeCallback)(ValueType index);
+    
+    void
+    Iterate (RangeCallback callback);
+    
+    ValueType
+    GetSize ();
+    
+    bool
+    IsEmpty ();
+    
+private:
+    
+    void
+    InitRange ();
+    
+    ValueType m_low;
+    ValueType m_high;
+};
+    
+} // namespace lldb_private
+
+#endif // #ifndef utility_Range_h_
diff --git a/lldb.xcodeproj/project.pbxproj b/lldb.xcodeproj/project.pbxproj
index eeb6b85..e128599 100644
--- a/lldb.xcodeproj/project.pbxproj
+++ b/lldb.xcodeproj/project.pbxproj
@@ -545,6 +545,8 @@
 		947A1D651616476B0017C8D1 /* CommandObjectPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 947A1D631616476A0017C8D1 /* CommandObjectPlugin.h */; };
 		949ADF031406F648004833E1 /* ValueObjectConstResultImpl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 949ADF021406F648004833E1 /* ValueObjectConstResultImpl.cpp */; };
 		94B6E76213D88365005F417F /* ValueObjectSyntheticFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94B6E76113D88362005F417F /* ValueObjectSyntheticFilter.cpp */; };
+		94BA8B6D176F8C9B005A91B5 /* Range.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94BA8B6C176F8C9B005A91B5 /* Range.cpp */; };
+		94BA8B70176F97CE005A91B5 /* CommandHistory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94BA8B6F176F97CE005A91B5 /* CommandHistory.cpp */; };
 		94CB255B16B069770059775D /* CXXFormatterFunctions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94CB255716B069770059775D /* CXXFormatterFunctions.cpp */; };
 		94CB255C16B069770059775D /* DataVisualization.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94CB255816B069770059775D /* DataVisualization.cpp */; };
 		94CB255D16B069770059775D /* FormatClasses.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94CB255916B069770059775D /* FormatClasses.cpp */; };
@@ -1601,6 +1603,10 @@
 		949ADF021406F648004833E1 /* ValueObjectConstResultImpl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ValueObjectConstResultImpl.cpp; path = source/Core/ValueObjectConstResultImpl.cpp; sourceTree = "<group>"; };
 		94B6E76013D8833C005F417F /* ValueObjectSyntheticFilter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ValueObjectSyntheticFilter.h; path = include/lldb/Core/ValueObjectSyntheticFilter.h; sourceTree = "<group>"; };
 		94B6E76113D88362005F417F /* ValueObjectSyntheticFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ValueObjectSyntheticFilter.cpp; path = source/Core/ValueObjectSyntheticFilter.cpp; sourceTree = "<group>"; };
+		94BA8B6C176F8C9B005A91B5 /* Range.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Range.cpp; path = source/Utility/Range.cpp; sourceTree = "<group>"; };
+		94BA8B6E176F8CA0005A91B5 /* Range.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Range.h; path = include/lldb/Utility/Range.h; sourceTree = "<group>"; };
+		94BA8B6F176F97CE005A91B5 /* CommandHistory.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CommandHistory.cpp; path = source/Interpreter/CommandHistory.cpp; sourceTree = "<group>"; };
+		94BA8B71176F97D4005A91B5 /* CommandHistory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = CommandHistory.h; path = include/lldb/Interpreter/CommandHistory.h; sourceTree = "<group>"; };
 		94CB255716B069770059775D /* CXXFormatterFunctions.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CXXFormatterFunctions.cpp; path = source/DataFormatters/CXXFormatterFunctions.cpp; sourceTree = "<group>"; };
 		94CB255816B069770059775D /* DataVisualization.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = DataVisualization.cpp; path = source/DataFormatters/DataVisualization.cpp; sourceTree = "<group>"; };
 		94CB255916B069770059775D /* FormatClasses.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = FormatClasses.cpp; path = source/DataFormatters/FormatClasses.cpp; sourceTree = "<group>"; };
@@ -2403,6 +2409,8 @@
 				26D1804616CEE12C00EDFB5B /* TimeSpecTimeout.h */,
 				26D1804016CEDF0700EDFB5B /* TimeSpecTimeout.cpp */,
 				94EBAC8313D9EE26009BA64E /* PythonPointer.h */,
+				94BA8B6E176F8CA0005A91B5 /* Range.h */,
+				94BA8B6C176F8C9B005A91B5 /* Range.cpp */,
 				B2462249141AE62200F3D409 /* Utils.h */,
 			);
 			name = Utility;
@@ -2946,6 +2954,8 @@
 				26A4EEB511682AAC007A372A /* LLDBWrapPython.cpp */,
 				4C09CB73116BD98B00C7A725 /* CommandCompletions.h */,
 				4C09CB74116BD98B00C7A725 /* CommandCompletions.cpp */,
+				94BA8B71176F97D4005A91B5 /* CommandHistory.h */,
+				94BA8B6F176F97CE005A91B5 /* CommandHistory.cpp */,
 				26BC7DE210F1B7F900F91463 /* CommandInterpreter.h */,
 				26BC7F0810F1B8DD00F91463 /* CommandInterpreter.cpp */,
 				26BC7DE310F1B7F900F91463 /* CommandObject.h */,
@@ -4091,6 +4101,7 @@
 				2689007413353E1A00698AC0 /* Terminal.cpp in Sources */,
 				2689007513353E1A00698AC0 /* TimeValue.cpp in Sources */,
 				2689007613353E1A00698AC0 /* CFCBundle.cpp in Sources */,
+				94BA8B70176F97CE005A91B5 /* CommandHistory.cpp in Sources */,
 				2689007713353E1A00698AC0 /* CFCData.cpp in Sources */,
 				2689007813353E1A00698AC0 /* CFCMutableArray.cpp in Sources */,
 				2689007913353E1A00698AC0 /* CFCMutableDictionary.cpp in Sources */,
@@ -4324,6 +4335,7 @@
 				260CC65215D0440D002BF2E0 /* OptionValueString.cpp in Sources */,
 				260CC65315D0440D002BF2E0 /* OptionValueUInt64.cpp in Sources */,
 				260CC65415D0440D002BF2E0 /* OptionValueUUID.cpp in Sources */,
+				94BA8B6D176F8C9B005A91B5 /* Range.cpp in Sources */,
 				26DAED6315D327C200E15819 /* OptionValuePathMappings.cpp in Sources */,
 				B2B7CCEB15D1BD6700EEFB57 /* CommandObjectWatchpointCommand.cpp in Sources */,
 				B2B7CCF015D1C20F00EEFB57 /* WatchpointOptions.cpp in Sources */,
diff --git a/source/Commands/CommandObjectCommands.cpp b/source/Commands/CommandObjectCommands.cpp
index 737a449..3e46041 100644
--- a/source/Commands/CommandObjectCommands.cpp
+++ b/source/Commands/CommandObjectCommands.cpp
@@ -22,10 +22,12 @@
 #include "lldb/Core/InputReaderEZ.h"
 #include "lldb/Core/StringList.h"
 #include "lldb/Interpreter/Args.h"
+#include "lldb/Interpreter/CommandHistory.h"
 #include "lldb/Interpreter/CommandInterpreter.h"
 #include "lldb/Interpreter/CommandObjectRegexCommand.h"
 #include "lldb/Interpreter/CommandReturnObject.h"
 #include "lldb/Interpreter/OptionValueBoolean.h"
+#include "lldb/Interpreter/OptionValueUInt64.h"
 #include "lldb/Interpreter/Options.h"
 #include "lldb/Interpreter/ScriptInterpreter.h"
 #include "lldb/Interpreter/ScriptInterpreterPython.h"
@@ -64,7 +66,11 @@
     public:
 
         CommandOptions (CommandInterpreter &interpreter) :
-            Options (interpreter)
+            Options (interpreter),
+            m_start_idx(0),
+            m_stop_idx(0),
+            m_count(0),
+            m_wipe(false)
         {
         }
 
@@ -76,27 +82,27 @@
         {
             Error error;
             const int short_option = m_getopt_table[option_idx].val;
-            bool success;
             
             switch (short_option)
             {
                 case 'c':
-                    m_end_idx = Args::StringToUInt32(option_arg, UINT_MAX, 0, &success);
-                    if (!success)
-                        error.SetErrorStringWithFormat("invalid value for count: %s", option_arg);
-                    if (m_end_idx != 0)
-                        m_end_idx--;
-                    m_start_idx = 0;
-                    break;
-                case 'e':
-                    m_end_idx = Args::StringToUInt32(option_arg, 0, 0, &success);
-                    if (!success)
-                        error.SetErrorStringWithFormat("invalid value for end index: %s", option_arg);
+                    error = m_count.SetValueFromCString(option_arg,eVarSetOperationAssign);
                     break;
                 case 's':
-                    m_start_idx = Args::StringToUInt32(option_arg, 0, 0, &success);
-                    if (!success)
-                        error.SetErrorStringWithFormat("invalid value for start index: %s", option_arg);
+                    if (option_arg && strcmp("end", option_arg) == 0)
+                    {
+                        m_start_idx.SetCurrentValue(UINT64_MAX);
+                        m_start_idx.SetOptionWasSet();
+                    }
+                    else
+                        error = m_start_idx.SetValueFromCString(option_arg,eVarSetOperationAssign);
+                    break;
+                case 'e':
+                    error = m_stop_idx.SetValueFromCString(option_arg,eVarSetOperationAssign);
+                    break;
+                case 'w':
+                    m_wipe.SetCurrentValue(true);
+                    m_wipe.SetOptionWasSet();
                     break;
                 default:
                     error.SetErrorStringWithFormat ("unrecognized option '%c'", short_option);
@@ -109,8 +115,10 @@
         void
         OptionParsingStarting ()
         {
-            m_start_idx = 0;
-            m_end_idx = UINT_MAX;
+            m_start_idx.Clear();
+            m_stop_idx.Clear();
+            m_count.Clear();
+            m_wipe.Clear();
         }
 
         const OptionDefinition*
@@ -125,17 +133,90 @@
 
         // Instance variables to hold the values for command options.
 
-        uint32_t m_start_idx;
-        uint32_t m_end_idx;
+        OptionValueUInt64 m_start_idx;
+        OptionValueUInt64 m_stop_idx;
+        OptionValueUInt64 m_count;
+        OptionValueBoolean m_wipe;
     };
     
     bool
     DoExecute (Args& command, CommandReturnObject &result)
     {
-        
-        m_interpreter.DumpHistory (result.GetOutputStream(),
-                                   m_options.m_start_idx, 
-                                   m_options.m_end_idx);
+        if (m_options.m_wipe.GetCurrentValue() && m_options.m_wipe.OptionWasSet())
+        {
+            m_interpreter.GetCommandHistory().Clear();
+            result.SetStatus(lldb::eReturnStatusSuccessFinishNoResult);
+        }
+        else
+        {
+            if (m_options.m_start_idx.OptionWasSet() && m_options.m_stop_idx.OptionWasSet() && m_options.m_count.OptionWasSet())
+            {
+                result.AppendError("--count, --start-index and --end-index cannot be all specified in the same invocation");
+                result.SetStatus(lldb::eReturnStatusFailed);
+            }
+            else
+            {
+                std::pair<bool,uint64_t> start_idx = {m_options.m_start_idx.OptionWasSet(),m_options.m_start_idx.GetCurrentValue()};
+                std::pair<bool,uint64_t> stop_idx = {m_options.m_stop_idx.OptionWasSet(),m_options.m_stop_idx.GetCurrentValue()};
+                std::pair<bool,uint64_t> count = {m_options.m_count.OptionWasSet(),m_options.m_count.GetCurrentValue()};
+                
+                const CommandHistory& history(m_interpreter.GetCommandHistory());
+                                              
+                if (start_idx.first && start_idx.second == UINT64_MAX)
+                {
+                    if (count.first)
+                    {
+                        start_idx.second = history.GetSize() - count.second;
+                        stop_idx.second = history.GetSize() - 1;
+                    }
+                    else if (stop_idx.first)
+                    {
+                        start_idx.second = stop_idx.second;
+                        stop_idx.second = history.GetSize() - 1;
+                    }
+                    else
+                    {
+                        start_idx.second = 0;
+                        stop_idx.second = history.GetSize() - 1;
+                    }
+                }
+                else
+                {
+                    if (!start_idx.first && !stop_idx.first && !count.first)
+                    {
+                        start_idx.second = 0;
+                        stop_idx.second = history.GetSize() - 1;
+                    }
+                    else if (start_idx.first)
+                    {
+                        if (count.first)
+                        {
+                            stop_idx.second = start_idx.second + count.second - 1;
+                        }
+                        else if (!stop_idx.first)
+                        {
+                            stop_idx.second = history.GetSize() - 1;
+                        }
+                    }
+                    else if (stop_idx.first)
+                    {
+                        if (count.first)
+                        {
+                            if (stop_idx.second >= count.second)
+                                start_idx.second = stop_idx.second - count.second + 1;
+                            else
+                                start_idx.second = 0;
+                        }
+                    }
+                    else /* if (count.first) */
+                    {
+                        start_idx.second = 0;
+                        stop_idx.second = count.second - 1;
+                    }
+                }
+                history.Dump(result.GetOutputStream(), start_idx.second, stop_idx.second);
+            }
+        }
         return result.Succeeded();
 
     }
@@ -147,8 +228,9 @@
 CommandObjectCommandsHistory::CommandOptions::g_option_table[] =
 {
 { LLDB_OPT_SET_1, false, "count", 'c', required_argument, NULL, 0, eArgTypeUnsignedInteger,        "How many history commands to print."},
-{ LLDB_OPT_SET_1, false, "start-index", 's', required_argument, NULL, 0, eArgTypeUnsignedInteger,  "Index at which to start printing history commands."},
+{ LLDB_OPT_SET_1, false, "start-index", 's', required_argument, NULL, 0, eArgTypeUnsignedInteger,  "Index at which to start printing history commands (or end to mean tail mode)."},
 { LLDB_OPT_SET_1, false, "end-index", 'e', required_argument, NULL, 0, eArgTypeUnsignedInteger,    "Index at which to stop printing history commands."},
+{ LLDB_OPT_SET_2, false, "wipe", 'w', no_argument, NULL, 0, eArgTypeBoolean,    "Clears the current command history."},
 { 0, false, NULL, 0, 0, NULL, 0, eArgTypeNone, NULL }
 };
 
diff --git a/source/Interpreter/CommandHistory.cpp b/source/Interpreter/CommandHistory.cpp
new file mode 100644
index 0000000..33971e3
--- /dev/null
+++ b/source/Interpreter/CommandHistory.cpp
@@ -0,0 +1,143 @@
+//===-- CommandHistory.cpp --------------------------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Interpreter/CommandHistory.h"
+#include "lldb/Interpreter/Args.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+
+CommandHistory::CommandHistory () :
+    m_mutex(Mutex::eMutexTypeRecursive),
+    m_history()
+{}
+
+CommandHistory::~CommandHistory ()
+{}
+
+size_t
+CommandHistory::GetSize () const
+{
+    Mutex::Locker locker(m_mutex);
+    return m_history.size();
+}
+
+bool
+CommandHistory::IsEmpty () const
+{
+    Mutex::Locker locker(m_mutex);
+    return m_history.empty();
+}
+
+const char*
+CommandHistory::FindString (const char* input_str) const
+{
+    Mutex::Locker locker(m_mutex);
+    if (!input_str)
+        return NULL;
+    if (input_str[0] != g_repeat_char)
+        return NULL;
+    if (input_str[1] == '-')
+    {
+        bool success;
+        size_t idx = Args::StringToUInt32 (input_str+2, 0, 0, &success);
+        if (!success)
+            return NULL;
+        if (idx > m_history.size())
+            return NULL;
+        idx = m_history.size() - idx;
+        return m_history[idx].c_str();
+        
+    }
+    else if (input_str[1] == g_repeat_char)
+    {
+        if (m_history.empty())
+            return NULL;
+        else
+            return m_history.back().c_str();
+    }
+    else
+    {
+        bool success;
+        uint32_t idx = Args::StringToUInt32 (input_str+1, 0, 0, &success);
+        if (!success)
+            return NULL;
+        if (idx >= m_history.size())
+            return NULL;
+        return m_history[idx].c_str();
+    }
+}
+
+const char*
+CommandHistory::GetStringAtIndex (size_t idx) const
+{
+    Mutex::Locker locker(m_mutex);
+    if (idx < m_history.size())
+        return m_history[idx].c_str();
+    return NULL;
+}
+
+const char*
+CommandHistory::operator [] (size_t idx) const
+{
+    return GetStringAtIndex(idx);
+}
+
+const char*
+CommandHistory::GetRecentmostString () const
+{
+    Mutex::Locker locker(m_mutex);
+    if (m_history.empty())
+        return NULL;
+    return m_history.back().c_str();
+}
+
+void
+CommandHistory::AppendString (const std::string& str,
+                              bool reject_if_dupe)
+{
+    Mutex::Locker locker(m_mutex);
+    if (reject_if_dupe)
+    {
+        if (!m_history.empty())
+        {
+            if (str == m_history.back())
+                return;
+        }
+    }
+    m_history.push_back(std::string(str));
+}
+
+void
+CommandHistory::Clear ()
+{
+    Mutex::Locker locker(m_mutex);
+    m_history.clear();
+}
+
+void
+CommandHistory::Dump (Stream& stream,
+                      size_t start_idx,
+                      size_t stop_idx) const
+{
+    Mutex::Locker locker(m_mutex);
+    stop_idx = std::min(stop_idx, m_history.size() - 1);
+    for (size_t counter = start_idx;
+         counter <= stop_idx;
+         counter++)
+    {
+        const std::string hist_item = m_history[counter];
+        if (!hist_item.empty())
+        {
+            stream.Indent();
+            stream.Printf ("%4zu: %s\n", counter, hist_item.c_str());
+        }
+    }
+}
diff --git a/source/Interpreter/CommandInterpreter.cpp b/source/Interpreter/CommandInterpreter.cpp
index aaf1b47..3dc5d29 100644
--- a/source/Interpreter/CommandInterpreter.cpp
+++ b/source/Interpreter/CommandInterpreter.cpp
@@ -107,7 +107,6 @@
     m_skip_app_init_files (false),
     m_script_interpreter_ap (),
     m_comment_char ('#'),
-    m_repeat_char ('!'),
     m_batch_command_mode (false),
     m_truncation_warning(eNoTruncation),
     m_command_source_depth (0)
@@ -1534,9 +1533,9 @@
             empty_command = true;
         else if (command_string[non_space] == m_comment_char)
              comment_command = true;
-        else if (command_string[non_space] == m_repeat_char)
+        else if (command_string[non_space] == CommandHistory::g_repeat_char)
         {
-            const char *history_string = FindHistoryString (command_string.c_str() + non_space);
+            const char *history_string = m_command_history.FindString(command_string.c_str() + non_space);
             if (history_string == NULL)
             {
                 result.AppendErrorWithFormat ("Could not find entry: %s in history", command_string.c_str());
@@ -1553,7 +1552,7 @@
     {
         if (repeat_on_empty_command)
         {
-            if (m_command_history.empty())
+            if (m_command_history.IsEmpty())
             {
                 result.AppendError ("empty command");
                 result.SetStatus(eReturnStatusFailed);
@@ -1797,9 +1796,7 @@
             else
                 m_repeat_command.assign(original_command_string.c_str());
             
-            // Don't keep pushing the same command onto the history...
-            if (m_command_history.empty() || m_command_history.back() != original_command_string) 
-                m_command_history.push_back (original_command_string);
+            m_command_history.AppendString (original_command_string);
         }
         
         command_string = revised_command_line.GetData();
@@ -1957,9 +1954,9 @@
     {
         if (first_arg[0] == m_comment_char)
             return 0;
-        else if (first_arg[0] == m_repeat_char)
+        else if (first_arg[0] == CommandHistory::g_repeat_char)
         {
-            const char *history_string = FindHistoryString (first_arg);
+            const char *history_string = m_command_history.FindString (first_arg);
             if (history_string != NULL)
             {
                 matches.Clear();
@@ -2878,59 +2875,3 @@
         m_exe_ctx_ref.SetTargetPtr (m_debugger.GetSelectedTarget().get(), adopt_selected);
     }
 }
-
-void
-CommandInterpreter::DumpHistory (Stream &stream, uint32_t count) const
-{
-    DumpHistory (stream, 0, count - 1);
-}
-
-void
-CommandInterpreter::DumpHistory (Stream &stream, uint32_t start, uint32_t end) const
-{
-    const size_t last_idx = std::min<size_t>(m_command_history.size(), end==UINT32_MAX ? UINT32_MAX : end + 1);
-    for (size_t i = start; i < last_idx; i++)
-    {
-        if (!m_command_history[i].empty())
-        {
-            stream.Indent();
-            stream.Printf ("%4zu: %s\n", i, m_command_history[i].c_str());
-        }
-    }
-}
-
-const char *
-CommandInterpreter::FindHistoryString (const char *input_str) const
-{
-    if (input_str[0] != m_repeat_char)
-        return NULL;
-    if (input_str[1] == '-')
-    {
-        bool success;
-        size_t idx = Args::StringToUInt32 (input_str+2, 0, 0, &success);
-        if (!success)
-            return NULL;
-        if (idx > m_command_history.size())
-            return NULL;
-        idx = m_command_history.size() - idx;
-        return m_command_history[idx].c_str();
-            
-    }
-    else if (input_str[1] == m_repeat_char)
-    {
-        if (m_command_history.empty())
-            return NULL;
-        else
-            return m_command_history.back().c_str();
-    }
-    else
-    {
-        bool success;
-        uint32_t idx = Args::StringToUInt32 (input_str+1, 0, 0, &success);
-        if (!success)
-            return NULL;
-        if (idx >= m_command_history.size())
-            return NULL;
-        return m_command_history[idx].c_str();
-    }
-}
diff --git a/source/Utility/Range.cpp b/source/Utility/Range.cpp
new file mode 100644
index 0000000..158d1e7
--- /dev/null
+++ b/source/Utility/Range.cpp
@@ -0,0 +1,103 @@
+//===--------------------- Range.cpp -----------------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Utility/Range.h"
+
+using namespace lldb_utility;
+
+Range::Range (const Range& rng) :
+m_low(rng.m_low),
+m_high(rng.m_high)
+{
+    InitRange();
+}
+
+Range::Range (Range::ValueType low,
+              Range::ValueType high) :
+m_low(low),
+m_high(high)
+{
+    InitRange();
+}
+
+void
+Range::InitRange ()
+{
+    if (m_low == OPEN_END)
+    {
+        if (m_high == OPEN_END)
+            m_low = 0;
+        else
+        {
+            // make an empty range
+            m_low = 1;
+            m_high = 0;
+        }
+    }
+}
+
+Range&
+Range::operator = (const Range& rhs)
+{
+    if (&rhs != this)
+    {
+        this->m_low = rhs.m_low;
+        this->m_high = rhs.m_high;
+    }
+    return *this;
+}
+
+void
+Range::Flip ()
+{
+    std::swap(m_high, m_low);
+}
+
+void
+Range::Intersection (const Range& other)
+{
+    m_low = std::max(m_low,other.m_low);
+    m_high = std::min(m_high,other.m_high);
+}
+
+void
+Range::Union (const Range& other)
+{
+    m_low = std::min(m_low,other.m_low);
+    m_high = std::max(m_high,other.m_high);
+}
+
+void
+Range::Iterate (RangeCallback callback)
+{
+    ValueType counter = m_low;
+    while (counter <= m_high)
+    {
+        bool should_continue = callback(counter);
+        if (!should_continue)
+            return;
+        counter++;
+    }
+}
+
+bool
+Range::IsEmpty ()
+{
+    return (m_low > m_high);
+}
+
+Range::ValueType
+Range::GetSize ()
+{
+    if (m_high == OPEN_END)
+        return OPEN_END;
+    if (m_high >= m_low)
+        return m_high - m_low + 1;
+    return 0;
+}
diff --git a/test/functionalities/command_history/TestCommandHistory.py b/test/functionalities/command_history/TestCommandHistory.py
new file mode 100644
index 0000000..b6b7d4d
--- /dev/null
+++ b/test/functionalities/command_history/TestCommandHistory.py
@@ -0,0 +1,72 @@
+"""
+Test the command history mechanism
+"""
+
+import os
+import unittest2
+import lldb
+import pexpect
+from lldbtest import *
+
+class CommandHistoryTestCase(TestBase):
+
+    mydir = os.path.join("functionalities", "command_history")
+
+    def test_history(self):
+        self.runCmd('command history --wipe', inHistory=False)
+        self.runCmd('breakpoint list', check=False, inHistory=True) #0
+        self.runCmd('register read', check=False, inHistory=True) #1
+        self.runCmd('apropos hello', check=False, inHistory=True) #2
+        self.runCmd('memory write', check=False, inHistory=True) #3
+        self.runCmd('script foo', check=False, inHistory=True) #4
+        self.runCmd('disassemble', check=False, inHistory=True) #5
+        self.runCmd('expression 1', check=False, inHistory=True) #6
+        self.runCmd('type summary list -w default', check=False, inHistory=True) #7
+        self.runCmd('version', check=False, inHistory=True) #8
+        self.runCmd('frame select 1', check=False, inHistory=True) #9
+
+        self.expect ("command history -s 3 -c 3", inHistory=True,
+                     substrs = ['3: memory write','4: script foo','5: disassemble'])
+        
+        self.expect ("command history -s 3 -e 3", inHistory=True,
+                     substrs = ['3: memory write'])
+
+        self.expect ("command history -s 6 -e 7", inHistory=True,
+                     substrs = ['6: expression 1','7: type summary list -w default'])
+
+        self.expect ("command history -c 2", inHistory=True,
+                     substrs = ['0: breakpoint list','1: register read'])
+
+        self.expect ("command history -e 3 -c 1", inHistory=True,
+                     substrs = ['3: memory write'])
+
+        self.expect ("command history -e 2", inHistory=True,
+                     substrs = ['0: breakpoint list','1: register read','2: apropos hello'])
+
+        self.expect ("command history -s 12", inHistory=True,
+                     substrs = ['12: command history -s 6 -e 7','13: command history -c 2','14: command history -e 3 -c 1','15: command history -e 2','16: command history -s 12'])
+
+        self.expect ("command history -s end -c 3", inHistory=True,
+                     substrs = ['15: command history -e 2','16: command history -s 12','17: command history -s end -c 3'])
+
+        self.expect ("command history -s end -e 15", inHistory=True,
+                     substrs = ['15: command history -e 2','16: command history -s 12','17: command history -s end -c 3','command history -s end -e 15'])
+
+        self.expect ("command history -s 5 -c 1", inHistory=True,
+                     substrs = ['5: disassemble'])
+
+        self.expect ("command history -c 1 -s 5", inHistory=True,
+                     substrs = ['5: disassemble'])
+
+        self.expect ("command history -c 1 -e 3", inHistory=True,
+                     substrs = ['3: memory write'])
+
+        self.expect ("command history -c 1 -e 3 -s 5",error=True, inHistory=True,
+                     substrs = ['error: --count, --start-index and --end-index cannot be all specified in the same invocation'])
+
+
+if __name__ == '__main__':
+    import atexit
+    lldb.SBDebugger.Initialize()
+    atexit.register(lambda: lldb.SBDebugger.Terminate())
+    unittest2.main()
diff --git a/test/lldbtest.py b/test/lldbtest.py
index 927175a..d2feedf 100644
--- a/test/lldbtest.py
+++ b/test/lldbtest.py
@@ -1543,7 +1543,7 @@
             if matched:
                 self.runCmd('thread select %s' % matched.group(1))
 
-    def runCmd(self, cmd, msg=None, check=True, trace=False):
+    def runCmd(self, cmd, msg=None, check=True, trace=False, inHistory=False):
         """
         Ask the command interpreter to handle the command and then check its
         return status.
@@ -1557,7 +1557,7 @@
         running = (cmd.startswith("run") or cmd.startswith("process launch"))
 
         for i in range(self.maxLaunchCount if running else 1):
-            self.ci.HandleCommand(cmd, self.res)
+            self.ci.HandleCommand(cmd, self.res, inHistory)
 
             with recording(self, trace) as sbuf:
                 print >> sbuf, "runCmd:", cmd
@@ -1624,7 +1624,7 @@
 
         return match_object        
 
-    def expect(self, str, msg=None, patterns=None, startstr=None, endstr=None, substrs=None, trace=False, error=False, matching=True, exe=True):
+    def expect(self, str, msg=None, patterns=None, startstr=None, endstr=None, substrs=None, trace=False, error=False, matching=True, exe=True, inHistory=False):
         """
         Similar to runCmd; with additional expect style output matching ability.
 
@@ -1653,7 +1653,7 @@
         if exe:
             # First run the command.  If we are expecting error, set check=False.
             # Pass the assert message along since it provides more semantic info.
-            self.runCmd(str, msg=msg, trace = (True if trace else False), check = not error)
+            self.runCmd(str, msg=msg, trace = (True if trace else False), check = not error, inHistory=inHistory)
 
             # Then compare the output against expected strings.
             output = self.res.GetError() if error else self.res.GetOutput()