Fix to add a little explanatory text to colors in the web config interface

This commit is contained in:
ridiculousfish 2012-05-08 17:10:38 -07:00
parent 4885842ae8
commit 4bd63020ca
2 changed files with 339 additions and 258 deletions

View file

@ -124,6 +124,8 @@ body {
/* Make our border overlap the detail, even if we're unselected (so it doesn't jump when selected) */ /* Make our border overlap the detail, even if we're unselected (so it doesn't jump when selected) */
position: relative; position: relative;
left: 1px; left: 1px;
border-bottom-style: solid;
border-bottom-width: 0px;
} }
.selected_master_elem { .selected_master_elem {
@ -143,9 +145,32 @@ body {
.master_element_text { .master_element_text {
text-decoration: none; text-decoration: none;
padding-bottom: 1px; padding-bottom: 1px;
border-bottom: 1px solid white; border-bottom-style: inherit;
border-bottom-color: inherit;
border-bottom-width: 1px;
} }
.master_element_description {
text-decoration: none;
padding-top: 15px;
font-size: 10pt;
border-bottom-style: inherit;
border-bottom-color: inherit;
border-bottom-width: 1px;
display: none;
}
.selected_master_elem > .master_element_description {
display: inline;
}
/* We have a newline between the label and description; hide it initially, but show it when it's selected */
.master_element > br { display: none; }
.selected_master_elem > br { display: inherit; }
/* Set this class to suppress the border bottom on master_element_texts with visible descriptions */
.master_element_no_border { border-bottom-width: 0 }
#colorpicker_term256 { #colorpicker_term256 {
border: solid #444 1px; border: solid #444 1px;
} }
@ -416,10 +441,12 @@ function switch_tab(new_tab) {
/* Keep track of whether this is the first element */ /* Keep track of whether this is the first element */
var first = true var first = true
run_get_request('/colors/', function(key_and_values){ run_get_request('/colors/', function(key_and_values){
/* Result is name, description, value */
var key = key_and_values[0] var key = key_and_values[0]
var style = new Style(key_and_values[1]) var description = key_and_values[1]
var style = new Style(key_and_values[2])
style_map[key] = style style_map[key] = style
elem = create_master_element(key, style.color, '', select_color_master_element) elem = create_master_element(key, description, style.color, '', select_color_master_element)
if (first) { if (first) {
/* It's the first element, so select it, so something gets selected */ /* It's the first element, so select it, so something gets selected */
select_color_master_element(elem) select_color_master_element(elem)
@ -432,7 +459,7 @@ function switch_tab(new_tab) {
/* Keep track of whether this is the first element */ /* Keep track of whether this is the first element */
var first = true var first = true
run_get_request('/functions/', function(contents){ run_get_request('/functions/', function(contents){
elem = create_master_element(contents, 'AAAAAA', '11pt', select_function_master_element) elem = create_master_element(contents, false/* description */, 'AAAAAA', '11pt', select_function_master_element)
if (first) { if (first) {
/* It's the first element, so select it, so something gets selected */ /* It's the first element, so select it, so something gets selected */
select_function_master_element(elem) select_function_master_element(elem)
@ -493,6 +520,7 @@ function reflect_style() {
/* Unselect everything */ /* Unselect everything */
$('.colorpicker_cell_selected').removeClass('colorpicker_cell_selected') $('.colorpicker_cell_selected').removeClass('colorpicker_cell_selected')
$('.modifier_cell_selected').removeClass('modifier_cell_selected') $('.modifier_cell_selected').removeClass('modifier_cell_selected')
$('.master_element_no_border').removeClass('master_element_no_border')
/* Now update the color picker with the current style (if we have one) */ /* Now update the color picker with the current style (if we have one) */
style = current_style() style = current_style()
@ -519,7 +547,16 @@ function reflect_style() {
/* In the master list, ensure the color is visible against the dark background. If we're deselecting, use COLOR_NORMAL */ /* In the master list, ensure the color is visible against the dark background. If we're deselecting, use COLOR_NORMAL */
master_color = style.color ? master_color_for_color(style.color) : COLOR_NORMAL master_color = style.color ? master_color_for_color(style.color) : COLOR_NORMAL
$('.selected_master_elem').children('.master_element_text').css({'color': master_color, 'border-bottom-color': master_color}) //$('.selected_master_elem').children('.master_element_text').css({'color': master_color, 'border-bottom-color': master_color})
var selected_elem = $('.selected_master_elem');
var desc_elems = selected_elem.children('.master_element_description')
selected_elem.css({'color': master_color})
selected_elem.children().css({'border-bottom-color': master_color})
if (desc_elems.length) {
/* We have a description element, so hide the bottom border of the master element */
selected_elem.children('.master_element_text').addClass('master_element_no_border')
}
} }
} }
@ -908,33 +945,51 @@ var show_labels = 0
var COLOR_NORMAL = 'CCC' var COLOR_NORMAL = 'CCC'
/* Adds a new element to master */ /* Adds a new element to master */
function create_master_element(contents, color, font_size, click_handler) { function create_master_element(contents, description_or_false, color, font_size, click_handler) {
/* In the master list, ensure the color is visible against the dark background */ /* In the master list, ensure the color is visible against the dark background */
var master_color = color ? master_color_for_color(color) : COLOR_NORMAL var master_color = color ? master_color_for_color(color) : COLOR_NORMAL
var style_str = 'color: #' + master_color + '; border-bottom: 1px solid #' + master_color + ' ;' var master_style = 'color: #' + master_color
var master_children_style = 'border-bottom-color: #' + master_color
var text_style = ''
if (font_size.length > 0) { if (font_size.length > 0) {
style_str += 'font-size: ' + font_size + ';' text_style += 'font-size: ' + font_size + ';'
} }
if (contents.length >= 20) { if (contents.length >= 20) {
style_str += 'letter-spacing:-2px;' text_style += 'letter-spacing:-2px;'
} }
elem = $('<div/>', { elem = $('<div/>', {
class: 'master_element', class: 'master_element',
id: 'master_' + contents, id: 'master_' + contents,
style: master_style,
click: function(){ click: function(){
click_handler(this) click_handler(this)
} }
}).append( }).append(
$("<span/>", { $("<span/>", {
class: 'master_element_text', class: 'master_element_text',
style: style_str, style: text_style,
text: contents, text: contents,
}) })
) )
/* Append description if we have one */
if (description_or_false) {
/* Newline between label and description */
elem.append($('<br/>'))
elem.append(
$('<span/>', {
class: 'master_element_description',
text: description_or_false
})
)
}
/* Update border color of the master element's children */
elem.children().css(master_children_style)
elem.appendTo('#master') elem.appendTo('#master')
return elem return elem
} }

View file

@ -7,248 +7,274 @@ import subprocess
import re, json, socket, os, sys, cgi, select import re, json, socket, os, sys, cgi, select
def run_fish_cmd(text): def run_fish_cmd(text):
from subprocess import PIPE from subprocess import PIPE
p = subprocess.Popen(["fish"], stdin=PIPE, stdout=PIPE, stderr=PIPE) p = subprocess.Popen(["fish"], stdin=PIPE, stdout=PIPE, stderr=PIPE)
out, err = p.communicate(text) out, err = p.communicate(text)
return out, err return out, err
named_colors = { named_colors = {
'black' : '000000', 'black' : '000000',
'red' : 'FF0000', 'red' : 'FF0000',
'green' : '00FF00', 'green' : '00FF00',
'brown' : '725000', 'brown' : '725000',
'yellow' : 'FFFF00', 'yellow' : 'FFFF00',
'blue' : '0000FF', 'blue' : '0000FF',
'magenta' : 'FF00FF', 'magenta' : 'FF00FF',
'purple' : 'FF00FF', 'purple' : 'FF00FF',
'cyan' : '00FFFF', 'cyan' : '00FFFF',
'white' : 'FFFFFF' 'white' : 'FFFFFF'
} }
def parse_one_color(comp): def parse_one_color(comp):
""" A basic function to parse a single color value like 'FFA000' """ """ A basic function to parse a single color value like 'FFA000' """
if comp in named_colors: if comp in named_colors:
# Named color # Named color
return named_colors[comp] return named_colors[comp]
elif re.match(r"[0-9a-fA-F]{3}", comp) is not None or re.match(r"[0-9a-fA-F]{6}", comp) is not None: elif re.match(r"[0-9a-fA-F]{3}", comp) is not None or re.match(r"[0-9a-fA-F]{6}", comp) is not None:
# Hex color # Hex color
return comp return comp
else: else:
# Unknown # Unknown
return '' return ''
def parse_color(color_str): def parse_color(color_str):
""" A basic function to parse a color string, for example, 'red' '--bold' """ """ A basic function to parse a color string, for example, 'red' '--bold' """
comps = color_str.split(' ') comps = color_str.split(' ')
color = 'normal' color = 'normal'
background_color = '' background_color = ''
bold, underline = False, False bold, underline = False, False
for comp in comps: for comp in comps:
# Remove quotes # Remove quotes
comp = comp.strip("'\" ") comp = comp.strip("'\" ")
if comp == '--bold': if comp == '--bold':
bold = True bold = True
elif comp == '--underline': elif comp == '--underline':
underline = True underline = True
elif comp.startswith('--background='): elif comp.startswith('--background='):
# Background color # Background color
background_color = parse_one_color(comp[len('--background='):]) background_color = parse_one_color(comp[len('--background='):])
else: else:
# Regular color # Regular color
maybe_color = parse_one_color(comp) maybe_color = parse_one_color(comp)
if maybe_color: color = maybe_color if maybe_color: color = maybe_color
return [color, background_color, bold, underline] return [color, background_color, bold, underline]
def parse_bool(val): def parse_bool(val):
val = val.lower() val = val.lower()
if val.startswith('f') or val.startswith('0'): return False if val.startswith('f') or val.startswith('0'): return False
if val.startswith('t') or val.startswith('1'): return True if val.startswith('t') or val.startswith('1'): return True
return bool(val) return bool(val)
class FishVar: class FishVar:
""" A class that represents a variable """ """ A class that represents a variable """
def __init__(self, name, value): def __init__(self, name, value):
self.name = name self.name = name
self.value = value self.value = value
self.universal = False self.universal = False
self.exported = False self.exported = False
def get_json_obj(self): def get_json_obj(self):
# Return an array(3): name, value, flags # Return an array(3): name, value, flags
flags = [] flags = []
if self.universal: flags.append('universal') if self.universal: flags.append('universal')
if self.exported: flags.append('exported') if self.exported: flags.append('exported')
return [self.name, self.value, ', '.join(flags)] return [self.name, self.value, ', '.join(flags)]
class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
def do_get_colors(self): def do_get_colors(self):
"Look for fish_color_*" # Looks for fish_color_*.
result = [] # Returns an array of lists [color_name, color_description, color_value]
remaining = set(['normal', result = []
'error',
'command',
'end',
'param',
'comment',
'match',
'search_match',
'operator',
'escape',
'quote',
'redirection',
'valid_path',
'autosuggestion'
])
out, err = run_fish_cmd('set -L') # Make sure we return at least these
for line in out.split('\n'): remaining = set(['normal',
for match in re.finditer(r"^fish_color_(\S+) ?(.*)", line): 'error',
color_name, color_value = [x.strip() for x in match.group(1, 2)] 'command',
result.append([color_name, parse_color(color_value)]) 'end',
remaining.discard(color_name) 'param',
'comment',
'match',
'search_match',
'operator',
'escape',
'quote',
'redirection',
'valid_path',
'autosuggestion'
])
# Ensure that we have all the color names we know about, so that if the # Here are our color descriptions
# user deletes one he can still set it again via the web interface descriptions = {
for x in remaining: 'normal': 'Default text',
result.append([x, parse_color('')]) 'command': 'Ordinary commands',
'quote': 'Text within quotes',
'redirection': 'Like | and >',
'end': 'Like ; and &',
'error': 'Potential errors',
'param': 'Command parameters',
'comment': 'Comments start with #',
'match': 'Matching parenthesis',
'search_match': 'History searching',
'history_current': 'Directory history',
'operator': 'Like * and ~',
'escape': 'Escapes like \\n',
'cwd': 'Current directory',
'cwd_root': 'cwd for root user',
'valid_path': 'Valid paths',
'autosuggestion': 'Suggested completion'
}
# Sort our result (by their keys) out, err = run_fish_cmd('set -L')
result.sort() for line in out.split('\n'):
for match in re.finditer(r"^fish_color_(\S+) ?(.*)", line):
color_name, color_value = [x.strip() for x in match.group(1, 2)]
color_desc = descriptions.get(color_name, '')
result.append([color_name, color_desc, parse_color(color_value)])
remaining.discard(color_name)
return result # Ensure that we have all the color names we know about, so that if the
# user deletes one he can still set it again via the web interface
for color_name in remaining:
color_desc = descriptions.get(color_name, '')
result.append([color_name, color_desc, parse_color('')])
def do_get_functions(self): # Sort our result (by their keys)
out, err = run_fish_cmd('functions') result.sort()
out = out.strip()
# Not sure why fish sometimes returns this with newlines return result
if "\n" in out:
return out.split('\n')
else:
return out.strip().split(', ')
def do_get_variable_names(self, cmd): def do_get_functions(self):
" Given a command like 'set -U' return all the variable names " out, err = run_fish_cmd('functions')
out, err = run_fish_cmd(cmd) out = out.strip()
return out.split('\n')
def do_get_variables(self): # Not sure why fish sometimes returns this with newlines
out, err = run_fish_cmd('set -L') if "\n" in out:
return out.split('\n')
else:
return out.strip().split(', ')
# Put all the variables into a dictionary def do_get_variable_names(self, cmd):
vars = {} " Given a command like 'set -U' return all the variable names "
for line in out.split('\n'): out, err = run_fish_cmd(cmd)
comps = line.split(' ', 1) return out.split('\n')
if len(comps) < 2: continue
fish_var = FishVar(comps[0], comps[1])
vars[fish_var.name] = fish_var
# Mark universal variables. L means don't abbreviate. def do_get_variables(self):
for name in self.do_get_variable_names('set -nUL'): out, err = run_fish_cmd('set -L')
if name in vars: vars[name].universal = True
# Mark exported variables. L means don't abbreviate.
for name in self.do_get_variable_names('set -nxL'):
if name in vars: vars[name].exported = True
return [vars[key].get_json_obj() for key in sorted(vars.keys(), key=str.lower)] # Put all the variables into a dictionary
vars = {}
for line in out.split('\n'):
comps = line.split(' ', 1)
if len(comps) < 2: continue
fish_var = FishVar(comps[0], comps[1])
vars[fish_var.name] = fish_var
def do_get_history(self): # Mark universal variables. L means don't abbreviate.
# Use \x1e ("record separator") to distinguish between history items. The first for name in self.do_get_variable_names('set -nUL'):
# backslash is so Python passes one backslash to fish if name in vars: vars[name].universal = True
out, err = run_fish_cmd('for val in $history; echo -n $val \\x1e; end') # Mark exported variables. L means don't abbreviate.
result = out.split('\x1e') for name in self.do_get_variable_names('set -nxL'):
if result: result.pop() if name in vars: vars[name].exported = True
return result
return [vars[key].get_json_obj() for key in sorted(vars.keys(), key=str.lower)]
def do_get_history(self):
# 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()
return result
def do_get_color_for_variable(self, name): def do_get_color_for_variable(self, name):
"Return the color with the given name, or the empty string if there is none" "Return the color with the given name, or the empty string if there is none"
out, err = run_fish_cmd("echo -n $" + name) out, err = run_fish_cmd("echo -n $" + name)
return out return out
def do_set_color_for_variable(self, name, color, background_color, bold, underline): def do_set_color_for_variable(self, name, color, background_color, bold, underline):
if not color: color = 'normal' if not color: color = 'normal'
"Sets a color for a fish color name, like 'autosuggestion'" "Sets a color for a fish color name, like 'autosuggestion'"
command = 'set -U fish_color_' + name command = 'set -U fish_color_' + name
if color: command += ' ' + color if color: command += ' ' + color
if background_color: command += ' --background=' + background_color if background_color: command += ' --background=' + background_color
if bold: command += ' --bold' if bold: command += ' --bold'
if underline: command += ' --underline' if underline: command += ' --underline'
out, err = run_fish_cmd(command) out, err = run_fish_cmd(command)
return out return out
def do_get_function(self, func_name): def do_get_function(self, func_name):
out, err = run_fish_cmd('functions ' + func_name) out, err = run_fish_cmd('functions ' + func_name)
return out return out
def do_GET(self): def do_GET(self):
p = self.path p = self.path
if p == '/colors/': if p == '/colors/':
output = self.do_get_colors() output = self.do_get_colors()
elif p == '/functions/': elif p == '/functions/':
output = self.do_get_functions() output = self.do_get_functions()
elif p == '/variables/': elif p == '/variables/':
output = self.do_get_variables() output = self.do_get_variables()
elif p == '/history/': elif p == '/history/':
output = self.do_get_history() output = self.do_get_history()
elif re.match(r"/color/(\w+)/", p): elif re.match(r"/color/(\w+)/", p):
name = re.match(r"/color/(\w+)/", p).group(1) name = re.match(r"/color/(\w+)/", p).group(1)
output = self.do_get_color_for_variable(name) output = self.do_get_color_for_variable(name)
else: else:
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
# Return valid output # Return valid output
self.send_response(200) self.send_response(200)
self.send_header('Content-type','text/html') self.send_header('Content-type','text/html')
self.wfile.write('\n') self.wfile.write('\n')
# Output JSON # Output JSON
json.dump(output, self.wfile) json.dump(output, self.wfile)
def do_POST(self): def do_POST(self):
p = self.path p = self.path
ctype, pdict = cgi.parse_header(self.headers.getheader('content-type')) ctype, pdict = cgi.parse_header(self.headers.getheader('content-type'))
if ctype == 'multipart/form-data': if ctype == 'multipart/form-data':
postvars = cgi.parse_multipart(self.rfile, pdict) postvars = cgi.parse_multipart(self.rfile, pdict)
elif ctype == 'application/x-www-form-urlencoded': elif ctype == 'application/x-www-form-urlencoded':
length = int(self.headers.getheader('content-length')) length = int(self.headers.getheader('content-length'))
postvars = cgi.parse_qs(self.rfile.read(length), keep_blank_values=1) postvars = cgi.parse_qs(self.rfile.read(length), keep_blank_values=1)
else: else:
postvars = {} postvars = {}
if p == '/set_color/': if p == '/set_color/':
what = postvars.get('what') what = postvars.get('what')
color = postvars.get('color') color = postvars.get('color')
background_color = postvars.get('background_color') background_color = postvars.get('background_color')
bold = postvars.get('bold') bold = postvars.get('bold')
underline = postvars.get('underline') underline = postvars.get('underline')
if what: if what:
# Not sure why we get lists here? # Not sure why we get lists here?
output = self.do_set_color_for_variable(what[0], color[0], background_color[0], parse_bool(bold[0]), parse_bool(underline[0])) output = self.do_set_color_for_variable(what[0], color[0], background_color[0], parse_bool(bold[0]), parse_bool(underline[0]))
else: else:
output = 'Bad request' output = 'Bad request'
elif p == '/get_function/': elif p == '/get_function/':
what = postvars.get('what') what = postvars.get('what')
output = [self.do_get_function(what[0])] output = [self.do_get_function(what[0])]
else: else:
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_POST(self) return SimpleHTTPServer.SimpleHTTPRequestHandler.do_POST(self)
# Return valid output # Return valid output
self.send_response(200) self.send_response(200)
self.send_header('Content-type','text/html') self.send_header('Content-type','text/html')
self.wfile.write('\n') self.wfile.write('\n')
# Output JSON # Output JSON
json.dump(output, self.wfile) json.dump(output, self.wfile)
def log_request(self, code='-', size='-'): def log_request(self, code='-', size='-'):
""" Disable request logging """ """ Disable request logging """
pass pass
# Make sure that the working directory is the one that contains the script server file, # Make sure that the working directory is the one that contains the script server file,
# because the document root is the working directory # because the document root is the working directory
@ -258,21 +284,21 @@ os.chdir(where)
# Try to find a suitable port # Try to find a suitable port
PORT = 8000 PORT = 8000
while PORT <= 9000: while PORT <= 9000:
try: try:
Handler = FishConfigHTTPRequestHandler Handler = FishConfigHTTPRequestHandler
httpd = SocketServer.TCPServer(("", PORT), Handler) httpd = SocketServer.TCPServer(("", PORT), Handler)
# Success # Success
break; break;
except socket.error: except socket.error:
type, value = sys.exc_info()[:2] type, value = sys.exc_info()[:2]
if 'Address already in use' not in value: if 'Address already in use' not in value:
break break
PORT += 1 PORT += 1
if PORT > 9000: if PORT > 9000:
# Nobody say it # Nobody say it
print "Unable to find an open port between 8000 and 9000" print "Unable to find an open port between 8000 and 9000"
sys.exit(-1) sys.exit(-1)
url = 'http://localhost:%d' % PORT url = 'http://localhost:%d' % PORT
@ -283,11 +309,11 @@ webbrowser.open(url)
# Select on stdin and httpd # Select on stdin and httpd
stdin_no = sys.stdin.fileno() stdin_no = sys.stdin.fileno()
while True: while True:
ready_read, _, _ = select.select([sys.stdin.fileno(), httpd.fileno()], [], []) ready_read, _, _ = select.select([sys.stdin.fileno(), httpd.fileno()], [], [])
if stdin_no in ready_read: if stdin_no in ready_read:
print "Shutting down." print "Shutting down."
# Consume the newline so it doesn't get printed by the caller # Consume the newline so it doesn't get printed by the caller
sys.stdin.readline() sys.stdin.readline()
break break
else: else:
httpd.handle_request() httpd.handle_request()