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

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 """