From ec34f2527a2c6738437a3339c490a9849c185247 Mon Sep 17 00:00:00 2001 From: Siteshwar Vashisht Date: Tue, 5 Jun 2012 09:54:42 +0530 Subject: [PATCH] Adding history builtin --- builtin.cpp | 127 ++++++++++++++++++++++++++++++++++- doc_src/history.txt | 36 ++++++++++ fish_tests.cpp | 7 +- history.cpp | 42 ++++++++++-- history.h | 14 +++- share/functions/history.fish | 101 ++++++++++++++++++++++++++++ 6 files changed, 316 insertions(+), 11 deletions(-) create mode 100644 doc_src/history.txt create mode 100644 share/functions/history.fish diff --git a/builtin.cpp b/builtin.cpp index 2cfec0470..662f4b6a3 100644 --- a/builtin.cpp +++ b/builtin.cpp @@ -5,7 +5,7 @@ 1). Create a function in builtin.c with the following signature: - static int builtin_NAME( wchar_t ** args ) + static int builtin_NAME( parser_t &parser, wchar_t ** args ) where NAME is the name of the builtin, and args is a zero-terminated list of arguments. @@ -63,7 +63,7 @@ #include "parser_keywords.h" #include "expand.h" #include "path.h" - +#include "history.h" /** The default prompt for the read command @@ -3590,6 +3590,126 @@ static int builtin_case( parser_t &parser, wchar_t **argv ) } +/** + History of commands executed by user +*/ +static int builtin_history( parser_t &parser, wchar_t **argv ) +{ + int argc = builtin_count_args(argv); + + bool search_history = false; + bool delete_item = false; + bool search_prefix = false; + bool save_history = false; + bool clear_history = false; + + wcstring delete_string; + wcstring search_string; + + static const struct woption long_options[] = + { + { L"prefix", required_argument, 0, 'p' }, + { L"delete", required_argument, 0, 'd' }, + { L"search", no_argument, 0, 's' }, + { L"contains", required_argument, 0, 'c' }, + { L"save", no_argument, 0, 'v' }, + { L"clear", no_argument, 0, 'l' }, + { 0, 0, 0, 0 } + }; + + int opt = 0; + int opt_index = 0; + woptind = 0; + history_t *history = reader_get_history(); + + while((opt = wgetopt_long( argc, argv, L"pdsc", long_options, &opt_index )) != -1) + { + switch(opt) + { + case 'p': + search_prefix = true; + search_string = woptarg; + break; + case 'd': + delete_item = true; + delete_string = woptarg; + break; + case 's': + search_history = true; + break; + case 'c': + search_string = woptarg; + break; + case 'v': + save_history = true; + break; + case 'l': + clear_history = true; + break; + case '?': + append_format(stderr_buffer, BUILTIN_ERR_UNKNOWN, argv[0], argv[woptind-1]); + return STATUS_BUILTIN_ERROR; + break; + default: + append_format(stderr_buffer, BUILTIN_ERR_UNKNOWN, argv[0], argv[woptind-1]); + return STATUS_BUILTIN_ERROR; + } + } + + if (argc == 1) + { + wcstring full_history; + history->get_string_representation(full_history, wcstring(L"\n")); + stdout_buffer.append(full_history); + return STATUS_BUILTIN_OK; + } + + if (search_history) + { + int res = STATUS_BUILTIN_ERROR; + + if (search_string.empty()) + { + append_format(stderr_buffer, BUILTIN_ERR_COMBO2, argv[0], L"Use --search with either --contains or --prefix"); + return res; + } + + history_search_t searcher = history_search_t(*history, search_string, search_prefix?HISTORY_SEARCH_TYPE_PREFIX:HISTORY_SEARCH_TYPE_CONTAINS); + while (searcher.go_backwards()) + { + stdout_buffer.append(searcher.current_string()); + stdout_buffer.append(L"\n"); + res = STATUS_BUILTIN_OK; + } + return res; + } + + if (delete_item) + { + if (delete_string[0] == '"' && delete_string[delete_string.length() - 1] == '"') + delete_string = delete_string.substr(1, delete_string.length() - 2); + + history->remove(delete_string); + return STATUS_BUILTIN_OK; + } + + if (save_history) + { + history->save(); + return STATUS_BUILTIN_OK; + } + + if (clear_history) + { + history->clear(); + history->save(); + return STATUS_BUILTIN_OK; + } + + return STATUS_BUILTIN_ERROR; +} + + /* END OF BUILTIN COMMANDS Below are functions for handling the builtin commands. @@ -3629,7 +3749,8 @@ static const builtin_data_t builtin_datas[]= { L"for", &builtin_for, N_( L"Perform a set of commands multiple times" ) }, { L"function", &builtin_function, N_( L"Define a new function" ) }, { L"functions", &builtin_functions, N_( L"List or remove functions" ) }, - { L"if", &builtin_generic, N_( L"Evaluate block if condition is true" ) }, + { L"history", &builtin_history, N_( L"History of commands executed by user" ) }, + { L"if", &builtin_generic, N_( L"Evaluate block if condition is true" ) }, { L"jobs", &builtin_jobs, N_( L"Print currently running jobs" ) }, { L"not", &builtin_generic, N_( L"Negate exit status of job" ) }, { L"or", &builtin_generic, N_( L"Execute command if previous command failed" ) }, diff --git a/doc_src/history.txt b/doc_src/history.txt new file mode 100644 index 000000000..fc6dd13b4 --- /dev/null +++ b/doc_src/history.txt @@ -0,0 +1,36 @@ +\section history history - Show and manipulate user's command history + +\subsection history-synopsis Synopsis +
+history (--save | --clear)
+history (--search | --delete ) (--prefix "prefix string" | --search "search string")
+
+ +\subsection history-description Description + +history is used to list, search and delete user's command history. + +\subsection history-examples Example + +
+history --save
+Save all changes in history file.
+
+history --clear
+Delete all history items.
+
+history --search --contains "foo"
+Searches commands containing "foo" string.
+
+history --search --prefix "foo"
+Searches for commands with prefix "foo".
+
+history --delete --contains "foo"
+Interactively delete commands containing string "foo".
+
+history --delete --prefix "foo"
+Interactively delete commands with prefix "foo".
+
+history --delete "foo"
+Delete command "foo" from history.
+
diff --git a/fish_tests.cpp b/fish_tests.cpp
index 002912acd..d99801949 100644
--- a/fish_tests.cpp
+++ b/fish_tests.cpp
@@ -863,7 +863,12 @@ void history_tests_t::test_history(void) {
     history_search_t search2(history, L"et");
     test_history_matches(search2, 1);
     assert(search2.current_string() == L"Beta");
-    
+
+    /* Test item removal */
+    history.remove(L"Alpha");
+    history_search_t search3(history, L"Alpha");
+    test_history_matches(search3, 0);
+   
     /* Test history escaping and unescaping, yaml, etc. */
     std::vector before, after;
     history.clear();
diff --git a/history.cpp b/history.cpp
index 547792f45..73d14fc2c 100644
--- a/history.cpp
+++ b/history.cpp
@@ -393,6 +393,23 @@ void history_t::add(const wcstring &str, const path_list_t &valid_paths)
     this->add(history_item_t(str, time(NULL), valid_paths));
 }
 
+void history_t::remove(const wcstring &str)
+{
+    history_item_t item_to_delete(str);
+    deleted_items.push_back(item_to_delete);
+
+    for (std::vector::iterator iter = new_items.begin(); iter != new_items.end(); ++iter)
+    {
+        if (iter->match_contents(item_to_delete))
+        {
+            new_items.erase(iter);
+            break;
+        }
+    }
+
+    save();
+}
+
 void history_t::get_string_representation(wcstring &result, const wcstring &separator)
 {
     scoped_lock locker(lock);
@@ -400,10 +417,10 @@ void history_t::get_string_representation(wcstring &result, const wcstring &sepa
     bool first = true;
     
     /* Append new items */
-    for (size_t i=0; i < new_items.size(); i++) {
+    for (std::vector::const_reverse_iterator iter=new_items.rbegin(); iter < new_items.rend(); ++iter) {
         if (! first)
             result.append(separator);
-        result.append(new_items.at(i).str());
+        result.append(iter->str());
         first = false;
     }
     
@@ -795,7 +812,7 @@ void history_t::save_internal()
     ASSERT_IS_LOCKED(lock);
     
     /* Nothing to do if there's no new items */
-    if (new_items.empty())
+    if (new_items.empty() && deleted_items.empty())
         return;
         
     /* Compact our new items so we don't have duplicates */
@@ -825,9 +842,11 @@ void history_t::save_internal()
 
                 /* Try decoding an old item */
                 const history_item_t old_item = history_t::decode_item(local_mmap_start + offset, local_mmap_size - offset);
-                if (old_item.empty())
+                if (old_item.empty() || is_deleted(old_item))
+                {
+//                    debug(0, L"Item is deleted : %s\n", old_item.str().c_str());
                     continue;
-                    
+                }
                 /* The old item may actually be more recent than our new item, if it came from another session. Insert all new items at the given index with an earlier timestamp. */
                 for (; new_item_iter != new_items.end(); ++new_item_iter) {
                     if (new_item_iter->timestamp() < old_item.timestamp()) {
@@ -846,7 +865,8 @@ void history_t::save_internal()
         }
         
         /* Insert any remaining new items */
-        for (; new_item_iter != new_items.end(); ++new_item_iter) {
+        for (; new_item_iter != new_items.end(); ++new_item_iter)
+        {
             lru.add_item(*new_item_iter);
         }
                 
@@ -903,6 +923,7 @@ void history_t::save(void) {
 void history_t::clear(void) {
     scoped_lock locker(lock);
     new_items.clear();
+    deleted_items.clear();
     unsaved_item_count = 0;
     old_item_offsets.clear();
     wcstring filename = history_filename(name, L"");
@@ -1020,3 +1041,12 @@ void history_t::add_with_file_detection(const wcstring &str)
     }
 }
 
+bool history_t::is_deleted(const history_item_t &item) const
+{
+    for (std::vector::const_iterator iter = deleted_items.begin(); iter != deleted_items.end(); ++iter)
+    { 
+        if (iter->match_contents(item))  { return true; }
+    }
+
+    return false;
+}
diff --git a/history.h b/history.h
index 2cadbcfa0..4b146c1f6 100644
--- a/history.h
+++ b/history.h
@@ -64,6 +64,10 @@ class history_item_t {
                creation_timestamp == other.creation_timestamp &&
                required_paths == other.required_paths;
     }
+
+    bool match_contents(const history_item_t &other) const {
+        return contents == other.contents;
+    }
     
     /* Functions for testing only */
     
@@ -96,7 +100,10 @@ private:
 	
 	/** New items. */
 	std::vector new_items;
-    
+
+  	/** Deleted items. */
+	std::vector deleted_items;
+
 	/** How many items we've added without saving */
 	size_t unsaved_item_count;
     
@@ -137,6 +144,9 @@ public:
     
     /** Add a new history item to the end */
     void add(const wcstring &str, const path_list_t &valid_paths = path_list_t());
+
+    /** Remove a history item */
+    void remove(const wcstring &str);
     
     /** Add a new history item to the end */
     void add_with_file_detection(const wcstring &str);
@@ -152,6 +162,8 @@ public:
     
     /** Return the specified history at the specified index. 0 is the index of the current commandline. (So the most recent item is at index 1.) */
     history_item_t item_at_index(size_t idx);
+
+    bool is_deleted(const history_item_t &item) const;
 };
 
 class history_search_t {
diff --git a/share/functions/history.fish b/share/functions/history.fish
new file mode 100644
index 000000000..71d714a5d
--- /dev/null
+++ b/share/functions/history.fish
@@ -0,0 +1,101 @@
+#
+#Deletes an item from history
+#
+
+function history --description "Deletes an item from history"
+
+    set -l argc (count $argv)
+    set -l prefix_args ""
+    set -l contains_args ""
+
+    set -l delete 0
+    set -l clear 0
+
+    set -l search_mode none
+
+    if test $argc -gt 0  
+        for i in (seq $argc)
+            switch $argv[$i]
+                case --delete
+                    set delete 1
+                case --prefix
+                    set search_mode prefix
+                    set prefix_args $argv[(math $i + 1)]
+                case --contains
+                    set search_mode contains
+                    set contains_args $argv[(math $i + 1)]
+                case --clear
+                    set clear 1
+            end;
+        end;
+    end; 
+
+    if test $delete = 1
+        set -l found_items ""
+        switch $search_mode
+            case prefix
+                set found_items (builtin history --search --prefix $prefix_args)
+            case contains
+                set found_items (builtin history --search --contains $contains_args)
+            case none
+                builtin history $argv
+                return 0 
+        end;
+        
+        set found_items_count (count $found_items)
+        if test $found_items_count -gt 0 
+            printf "Delete which entries ?\n"
+            printf "[0] cancel\n"
+            printf "[1] all\n\n"
+            
+            for i in (seq $found_items_count)
+                printf "[%s] %s \n" (math $i + 1) $found_items[$i]
+            end;
+
+            read choice
+            set choice (echo $choice | tr " " "\n") 
+
+            for i in $choice
+                echo $i | grep -q "^[0-9]*\$"
+
+                #Following two validations could be embedded with "and" but I find the syntax kind of weird. 
+                if test $status -ne 0
+                    printf "Invalid input: %s\n" $i 
+                    continue; 
+                end;
+ 
+                if test $i -gt (math $found_items_count + 1)
+                    printf "Invalid input : %s\n" $i 
+                    continue
+                end;
+                
+                if test $i = "0"
+                    printf "Cancel\n"
+                    return 
+                else
+                    if test $i = "1"
+                        for item in $found_items
+                            builtin history --delete $item
+                        end;
+                        printf "Deleted all!\n"
+                        return 
+                    else
+                        builtin history --delete $found_items[(math $i - 1)]
+                    end;
+                end;
+            end;
+         end;
+    else
+        if test $clear = 1
+            echo "Are you sure you want to clear history ? (y/n)"
+            read ch
+            if test $ch = "y"
+                builtin history $argv
+                echo "History cleared!"
+            end;
+        else 
+            builtin history $argv
+        end;
+    end; 
+
+end;