Implemented history deletion from fish_config

Fixes https://github.com/fish-shell/fish-shell/issues/250
This commit is contained in:
ridiculousfish 2012-07-27 00:31:00 -07:00
parent 390700ca71
commit e7cbcc83a4
6 changed files with 261 additions and 9353 deletions

View file

@ -29,6 +29,8 @@
/* Begin PBXBuildFile section */
D07A7D3C15A7A38100811FC6 /* builtin_scripts.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0F5E28215A7A32D00315DFF /* builtin_scripts.cpp */; };
D07B247315BCC15700D4ADB4 /* add-shell in Resources */ = {isa = PBXBuildFile; fileRef = D07B247215BCC15700D4ADB4 /* add-shell */; };
D07B247615BCC4BE00D4ADB4 /* install.sh in Resources */ = {isa = PBXBuildFile; fileRef = D07B247515BCC4BE00D4ADB4 /* install.sh */; };
D0C4FD9515A7D80700212EF1 /* config.fish in CopyFiles */ = {isa = PBXBuildFile; fileRef = D0C4FD9415A7D7EE00212EF1 /* config.fish */; };
D0C4FD9615A7D80C00212EF1 /* config.fish in CopyFiles */ = {isa = PBXBuildFile; fileRef = D0CBD580159EE48F0024809C /* config.fish */; };
D0CBD57B159EE4640024809C /* completions in CopyFiles */ = {isa = PBXBuildFile; fileRef = D0CBD578159EE4600024809C /* completions */; };
@ -279,6 +281,8 @@
/* Begin PBXFileReference section */
D03EE83814DF88B200FC7150 /* lru.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = lru.h; sourceTree = "<group>"; };
D07B247215BCC15700D4ADB4 /* add-shell */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = "add-shell"; path = "build_tools/osx_package_scripts/add-shell"; sourceTree = "<group>"; };
D07B247515BCC4BE00D4ADB4 /* install.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = install.sh; path = osx/install.sh; sourceTree = "<group>"; };
D09B1C1914FC7B5B00F91077 /* postfork.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = postfork.cpp; sourceTree = "<group>"; };
D09B1C1A14FC7B5B00F91077 /* postfork.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = postfork.h; sourceTree = "<group>"; };
D0A0850313B3ACEE0099B651 /* builtin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = builtin.h; sourceTree = "<group>"; };
@ -599,9 +603,11 @@
D0D02AAB15985C14008E62BD /* Resources */ = {
isa = PBXGroup;
children = (
D07B247215BCC15700D4ADB4 /* add-shell */,
D0CBD586159EF0E10024809C /* launch_fish.scpt */,
D0CBD580159EE48F0024809C /* config.fish */,
D0C4FD9415A7D7EE00212EF1 /* config.fish */,
D07B247515BCC4BE00D4ADB4 /* install.sh */,
D0CBD578159EE4600024809C /* completions */,
D0CBD579159EE4600024809C /* functions */,
D0CBD57A159EE4600024809C /* tools */,
@ -805,6 +811,8 @@
buildActionMask = 2147483647;
files = (
D0CBD587159EF0E10024809C /* launch_fish.scpt in Resources */,
D07B247315BCC15700D4ADB4 /* add-shell in Resources */,
D07B247615BCC4BE00D4ADB4 /* install.sh in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View file

@ -3639,15 +3639,12 @@ static int builtin_history( parser_t &parser, wchar_t **argv )
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"prefix", no_argument, 0, 'p' },
{ L"delete", no_argument, 0, 'd' },
{ L"search", no_argument, 0, 's' },
{ L"contains", required_argument, 0, 'c' },
{ L"contains", no_argument, 0, 'c' },
{ L"save", no_argument, 0, 'v' },
{ L"clear", no_argument, 0, 'l' },
{ L"help", no_argument, 0, 'h' },
@ -3658,24 +3655,25 @@ static int builtin_history( parser_t &parser, wchar_t **argv )
int opt_index = 0;
woptind = 0;
history_t *history = reader_get_history();
/* Use the default history if we have none (which happens if invoked non-interactively, e.g. from webconfig.py */
if (! history)
history = &history_t::history_with_name(L"fish");
while((opt = wgetopt_long_only( argc, argv, L"pdscvl", 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;
@ -3687,6 +3685,9 @@ static int builtin_history( parser_t &parser, wchar_t **argv )
builtin_print_help( parser, argv[0], stdout_buffer );
return STATUS_BUILTIN_OK;
break;
case EOF:
/* Remainder are arguments */
break;
case '?':
append_format(stderr_buffer, BUILTIN_ERR_UNKNOWN, argv[0], argv[woptind-1]);
return STATUS_BUILTIN_ERROR;
@ -3697,6 +3698,9 @@ static int builtin_history( parser_t &parser, wchar_t **argv )
}
}
/* Everything after is an argument */
const wcstring_list_t args(argv + woptind, argv + argc);
if (argc == 1)
{
wcstring full_history;
@ -3709,29 +3713,36 @@ static int builtin_history( parser_t &parser, wchar_t **argv )
if (search_history)
{
int res = STATUS_BUILTIN_ERROR;
if (search_string.empty())
for (wcstring_list_t::const_iterator iter = args.begin(); iter != args.end(); ++iter)
{
append_format(stderr_buffer, BUILTIN_ERR_COMBO2, argv[0], L"Use --search with either --contains or --prefix");
return res;
}
const wcstring &search_string = *iter;
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;
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);
for (wcstring_list_t::const_iterator iter = args.begin(); iter != args.end(); ++iter)
{
wcstring delete_string = *iter;
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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -106,6 +106,7 @@ body {
padding-left: 30px;
padding-right: 30px;
border-radius: 5;
width: 100%;
}
#detail_function {
@ -221,6 +222,11 @@ body {
word-wrap: break-word;
}
/* The CSS we apply when a table row is filtered */
.data_table_row_filtered {
display: none;
}
.no_overflow {
text-overflow: ellipsis;
white-space: nowrap;
@ -275,6 +281,37 @@ body {
margin-bottom: 5pt;
}
img.delete_icon {
width: 20px;
height: 20px;
cursor: pointer;
text-decoration: none;
border: none;
}
#table_filter_container {
/* top right bottom left*/
padding: 0 10 10 30;
text-align: right;
position: relative;
bottom: 10px;
}
.table_filter_text_box {
width: 250px;
padding: 5 10 5 10;
background-color: #888;
border: #222 solid 3px;
border-radius: 15px;
font-size: 12pt;
color: white;
font-weight: bold;
}
.text_box_transient {
color: #C8C8C8;
}
</style>
<script type="text/javascript" src="jquery.js"></script>
@ -307,20 +344,27 @@ function request_failed(jqXHR, textStatus, errorThrown) {
}
/* Runs a GET request, parses the JSON, and invokes the handler for each element in it. The JSON result is assumed to be an array. */
function run_get_request(url, handler) {
function run_get_request_with_bulk_handler(url, handler) {
$.ajax({
type: "GET",
url: url,
success: function(data){
$('#global_error').text('')
$.each($.parseJSON(data), function(idx, contents) {
handler(contents)
})
handler($.parseJSON(data))
},
error: request_failed
})
}
function run_get_request(url, handler) {
run_get_request_with_bulk_handler(url, function(json_contents){
$.each(json_contents, function(idx, contents){
handler(contents)
})
})
}
/* As above but with POST request. */
function run_post_request(url, data_map, handler) {
$.ajax({
@ -469,16 +513,33 @@ function switch_tab(new_tab) {
$('#detail_function').show()
$('#master_detail_table').show()
} else if (new_tab == 'tab_variables') {
run_get_request('/variables/', function(contents){
var name = contents[0]
var value = contents[1]
var flags = contents[2]
create_data_table_element([name, value])
run_get_request_with_bulk_handler('/variables/', function(json_contents){
var rows = new Array()
for (var i = 0; i < json_contents.length; i++) {
var contents = json_contents[i]
var name = contents[0]
var value = contents[1]
var flags = contents[2]
var row = create_data_table_element_text([name, value], false)
rows[i] = row
}
$('#data_table').append(rows.join(''))
})
$('#data_table').show()
} else if (new_tab == 'tab_history') {
run_get_request('/history/', function(contents){
create_data_table_element([contents])
// Clear the history map
history_element_map.length = 0
run_get_request_with_bulk_handler('/history/', function(json_contents){
start = new Date().getTime()
var rows = new Array()
for (var i = 0; i < json_contents.length; i++) {
var history_text = json_contents[i]
rows[i] = create_data_table_element_text([history_text], true)
history_element_map[last_global_element_identifier] = history_text
}
$('#data_table').append(rows.join(''))
end = new Date().getTime()
//alert(rows.length + " rows in " + (end - start) + " msec")
})
$('#data_table').show()
} else {
@ -999,36 +1060,53 @@ function toggle_overflow(who) {
$(who).toggleClass('no_overflow')
}
function escape_HTML(foo) {
return foo.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
/* Given the image, walk up to the table */
function tell_fish_to_delete_element(idx) {
var row_elem = $('#data_table_row_' + idx)
var txt = history_element_map[idx]
run_post_request('/delete_history_item/', {
what: txt
}, function(contents){
if (contents == "OK") {
row_elem.remove();
} else {
show_error(contents)
}
});
}
/* Creates a new row in the data table */
function create_data_table_element(contents_list) {
var row = $('<tr>', {
class: 'data_table_row'
})
var last_global_element_identifier = 0
var history_element_map = new Array();
function create_data_table_element_text(contents_list, show_delete_button) {
var element_identifier = (++last_global_element_identifier).toString()
lines = new Array()
var result_str = '<tr class="data_table_row" id="data_table_row_' + element_identifier + '">'
for (idx = 0; idx < contents_list.length; idx++) {
/* If we have more than one, then align the first one right, subsequent ones left */
if (idx == 0 && contents_list.length > 1) {
cell = $('<td>', {
class: 'data_table_cell no_overflow',
style: 'text-align: right; padding-right: 30px;'
});
result_str += '<td class="data_table_cell no_overflow" style="text-align: right; padding-right: 30px;">'
} else {
cell = $('<td>', {
class: 'data_table_cell no_overflow',
style: 'text-align: left',
onClick: 'toggle_overflow(this)'
});
result_str += '<td class="data_table_cell no_overflow" style="text-align: left; padding-right: 30px;" onClick:"toggle_overflow(this)">'
}
text_list = contents_list[idx].split("\n")
for (j=0; j < text_list.length; j++) {
cell.append($('<p>', {
text: text_list[j]
}))
if (j > 0) result_str += '<br>'
result_str += escape_HTML(text_list[j]);
}
row.append(cell)
result_str += '</td>'
}
$('#data_table').append(row)
if (show_delete_button) {
result_str += '<td class="data_table_cell" style="text-align: right; width: 25px"><a onClick="tell_fish_to_delete_element(' + element_identifier + ')"><img class="delete_icon" src="delete.png"></a></td>'
}
result_str += '</tr>'
return result_str
}
/* Put stuff in colorpicker_term256 */
@ -1061,6 +1139,54 @@ function populate_colorpicker_term256() {
}
}
/* Update the filter text box */
function update_table_filter_text_box(allow_transient_message) {
var box = $('.table_filter_text_box')
var has_transient = box.hasClass('text_box_transient')
if (! allow_transient_message && has_transient) {
box.val('')
box.removeClass('text_box_transient')
has_transient = false
} else if (allow_transient_message && ! has_transient && ! box.val().length) {
box.val('Filter')
box.addClass('text_box_transient')
has_transient = true
}
var search_text = box.val()
if (has_transient || search_text.length == 0) {
/* Unfilter all */
$('.data_table_row_filtered').removeClass('data_table_row_filtered')
} else {
/* Helper function to return whether a node (or its descendants) matches the given text */
function match_text(node) {
if (node.nodeType == 3) {
return node.nodeValue.indexOf(search_text) != -1
} else {
for (var i = 0, len = node.childNodes.length; i < len; ++i) {
if (match_text(node.childNodes[i])) {
return true;
}
}
}
return false
}
$('.data_table_row').each(function(idx) {
var row = $(this)
var is_hidden = row.hasClass('data_table_row_filtered')
var should_be_hidden = ! match_text(this)
if (is_hidden && ! should_be_hidden) {
row.removeClass('data_table_row_filtered')
} else if (! is_hidden && should_be_hidden) {
row.addClass('data_table_row_filtered')
}
})
}
return true
}
$(document).ready(function() {
populate_colorpicker_term256()
switch_tab('tab_colors')
@ -1096,7 +1222,13 @@ $(document).ready(function() {
<div id="detail_function"></div>
</div>
</div>
<table id="data_table"><tr><td></td></tr>
<table id="data_table">
<div id="table_filter_container">
<input type="text" class="table_filter_text_box text_box_transient" value="Filter" onInput="update_table_filter_text_box(false)" onFocus="update_table_filter_text_box(false)" onBlur="update_table_filter_text_box(true)">
</div>
<tr><td>
</td></tr>
<tr><td></td></tr>
</table>
<div class="footer">
</div>

File diff suppressed because one or more lines are too long

View file

@ -1,27 +1,34 @@
#!/usr/bin/env python
try: #Python2
# Whether we're Python 2
import sys
IS_PY2 = sys.version_info[0] == 2
if IS_PY2:
import SimpleHTTPServer
except ImportError: #Python3
import http.server as SimpleHTTPServer
try: #Python2
import SocketServer
except ImportError: #Python3
else:
import http.server as SimpleHTTPServer
import socketserver as SocketServer
import webbrowser
import subprocess
import re, json, socket, os, sys, cgi, select
import re, json, socket, os, sys, cgi, select, time
def run_fish_cmd(text):
from subprocess import PIPE
p = subprocess.Popen(["fish"], stdin=PIPE, stdout=PIPE, stderr=PIPE)
try: #Python2
out, err = p.communicate(text)
except TypeError: #Python3
if IS_PY2:
out, err = p.communicate(text)
else:
out, err = p.communicate(bytes(text, 'utf-8'))
out = str(out, 'utf-8')
err = str(err, 'utf-8')
return(out, err)
def escape_fish_cmd(text):
# Replace one backslash with two, and single quotes with backslash-quote
escaped = text.replace('\\', '\\\\').replace("'", "\\'")
return "'" + escaped + "'"
named_colors = {
'black' : '000000',
@ -105,6 +112,13 @@ class FishVar:
class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
def write_to_wfile(self, txt):
if IS_PY2:
self.wfile.write(txt)
else: # Python 3
self.wfile.write(bytes(txt, 'utf-8'))
def do_get_colors(self):
# Looks for fish_color_*.
# Returns an array of lists [color_name, color_description, color_value]
@ -207,8 +221,8 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
# Use \x1e ("record separator") to distinguish between history items. The first
# backslash is so Python passes one backslash to fish
out, err = run_fish_cmd('for val in $history; echo -n $val \\x1e; end')
result = out.split('\x1e')
if result: result.pop()
result = out.split(' \x1e')
if result: result.pop() # Trim off the trailing element
return result
@ -233,6 +247,11 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
out, err = run_fish_cmd('functions ' + func_name)
return out
def do_delete_history_item(self, history_item_text):
# It's really lame that we always return success here
out, err = run_fish_cmd('builtin history --save --delete -- ' + escape_fish_cmd(history_item_text))
return True
def do_GET(self):
p = self.path
if p == '/colors/':
@ -242,7 +261,10 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
elif p == '/variables/':
output = self.do_get_variables()
elif p == '/history/':
# start = time.time()
output = self.do_get_history()
# end = time.time()
# print "History: ", end - start
elif re.match(r"/color/(\w+)/", p):
name = re.match(r"/color/(\w+)/", p).group(1)
output = self.do_get_color_for_variable(name)
@ -252,22 +274,16 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
# Return valid output
self.send_response(200)
self.send_header('Content-type','text/html')
try: #Python2
self.wfile.write('\n')
except TypeError: #Python3
self.wfile.write(bytes('\n', 'utf-8'))
self.write_to_wfile('\n')
# Output JSON
try: #Python2
self.wfile.write(json.dumps(output))
except TypeError: #Python3
self.wfile.write(bytes(json.dumps(output), 'utf-8'))
self.write_to_wfile(json.dumps(output))
def do_POST(self):
p = self.path
try: #Python2
if IS_PY2:
ctype, pdict = cgi.parse_header(self.headers.getheader('content-type'))
except AttributeError: #Python3
else: # Python 3
ctype, pdict = cgi.parse_header(self.headers['content-type'])
if ctype == 'multipart/form-data':
postvars = cgi.parse_multipart(self.rfile, pdict)
@ -310,22 +326,25 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
what = postvars.get(b'what')
what[0] = str(what[0]).lstrip("b'").rstrip("'")
output = [self.do_get_function(what[0])]
elif p == '/delete_history_item/':
what = postvars.get('what')
if what == None: #Will be None for python3
what = postvars.get(b'what')
what[0] = str(what[0]).lstrip("b'").rstrip("'")
if self.do_delete_history_item(what[0]):
output = ["OK"]
else:
output = ["Unable to delete history item"]
else:
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_POST(self)
# Return valid output
self.send_response(200)
self.send_header('Content-type','text/html')
try: #Python2
self.wfile.write('\n')
except TypeError: #Python3
self.wfile.write(bytes('\n', 'utf-8'))
self.write_to_wfile('\n')
# Output JSON
try: #Python2
self.wfile.write(json.dumps(output))
except TypeError: #Python3
self.wfile.write(bytes(json.dumps(output), 'utf-8'))
self.write_to_wfile(json.dumps(output))
def log_request(self, code='-', size='-'):
""" Disable request logging """