diff --git a/share/tools/web_config/index.html b/share/tools/web_config/index.html
index a16b44ce0..cd167f3ee 100644
--- a/share/tools/web_config/index.html
+++ b/share/tools/web_config/index.html
@@ -124,6 +124,8 @@ body {
/* Make our border overlap the detail, even if we're unselected (so it doesn't jump when selected) */
position: relative;
left: 1px;
+ border-bottom-style: solid;
+ border-bottom-width: 0px;
}
.selected_master_elem {
@@ -143,9 +145,32 @@ body {
.master_element_text {
text-decoration: none;
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 {
border: solid #444 1px;
}
@@ -416,10 +441,12 @@ function switch_tab(new_tab) {
/* Keep track of whether this is the first element */
var first = true
run_get_request('/colors/', function(key_and_values){
+ /* Result is name, description, value */
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
- 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) {
/* It's the first element, so select it, so something gets selected */
select_color_master_element(elem)
@@ -432,7 +459,7 @@ function switch_tab(new_tab) {
/* Keep track of whether this is the first element */
var first = true
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) {
/* It's the first element, so select it, so something gets selected */
select_function_master_element(elem)
@@ -493,6 +520,7 @@ function reflect_style() {
/* Unselect everything */
$('.colorpicker_cell_selected').removeClass('colorpicker_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) */
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 */
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'
/* 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 */
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) {
- style_str += 'font-size: ' + font_size + ';'
+ text_style += 'font-size: ' + font_size + ';'
}
if (contents.length >= 20) {
- style_str += 'letter-spacing:-2px;'
+ text_style += 'letter-spacing:-2px;'
}
elem = $('
', {
class: 'master_element',
id: 'master_' + contents,
+ style: master_style,
click: function(){
click_handler(this)
}
}).append(
$("", {
class: 'master_element_text',
- style: style_str,
+ style: text_style,
text: contents,
})
)
+ /* Append description if we have one */
+ if (description_or_false) {
+ /* Newline between label and description */
+ elem.append($('
'))
+ elem.append(
+ $('', {
+ 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')
return elem
}
diff --git a/share/tools/web_config/webconfig.py b/share/tools/web_config/webconfig.py
index 20e5514fa..1acf64a97 100755
--- a/share/tools/web_config/webconfig.py
+++ b/share/tools/web_config/webconfig.py
@@ -7,248 +7,274 @@ import subprocess
import re, json, socket, os, sys, cgi, select
def run_fish_cmd(text):
- from subprocess import PIPE
- p = subprocess.Popen(["fish"], stdin=PIPE, stdout=PIPE, stderr=PIPE)
- out, err = p.communicate(text)
- return out, err
+ from subprocess import PIPE
+ p = subprocess.Popen(["fish"], stdin=PIPE, stdout=PIPE, stderr=PIPE)
+ out, err = p.communicate(text)
+ return out, err
named_colors = {
- 'black' : '000000',
- 'red' : 'FF0000',
- 'green' : '00FF00',
- 'brown' : '725000',
- 'yellow' : 'FFFF00',
- 'blue' : '0000FF',
- 'magenta' : 'FF00FF',
- 'purple' : 'FF00FF',
- 'cyan' : '00FFFF',
- 'white' : 'FFFFFF'
+ 'black' : '000000',
+ 'red' : 'FF0000',
+ 'green' : '00FF00',
+ 'brown' : '725000',
+ 'yellow' : 'FFFF00',
+ 'blue' : '0000FF',
+ 'magenta' : 'FF00FF',
+ 'purple' : 'FF00FF',
+ 'cyan' : '00FFFF',
+ 'white' : 'FFFFFF'
}
def parse_one_color(comp):
- """ A basic function to parse a single color value like 'FFA000' """
- if comp in named_colors:
- # Named color
- 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:
- # Hex color
- return comp
- else:
- # Unknown
- return ''
+ """ A basic function to parse a single color value like 'FFA000' """
+ if comp in named_colors:
+ # Named color
+ 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:
+ # Hex color
+ return comp
+ else:
+ # Unknown
+ return ''
def parse_color(color_str):
- """ A basic function to parse a color string, for example, 'red' '--bold' """
- comps = color_str.split(' ')
- color = 'normal'
- background_color = ''
- bold, underline = False, False
- for comp in comps:
- # Remove quotes
- comp = comp.strip("'\" ")
- if comp == '--bold':
- bold = True
- elif comp == '--underline':
- underline = True
- elif comp.startswith('--background='):
- # Background color
- background_color = parse_one_color(comp[len('--background='):])
- else:
- # Regular color
- maybe_color = parse_one_color(comp)
- if maybe_color: color = maybe_color
-
- return [color, background_color, bold, underline]
-
-
+ """ A basic function to parse a color string, for example, 'red' '--bold' """
+ comps = color_str.split(' ')
+ color = 'normal'
+ background_color = ''
+ bold, underline = False, False
+ for comp in comps:
+ # Remove quotes
+ comp = comp.strip("'\" ")
+ if comp == '--bold':
+ bold = True
+ elif comp == '--underline':
+ underline = True
+ elif comp.startswith('--background='):
+ # Background color
+ background_color = parse_one_color(comp[len('--background='):])
+ else:
+ # Regular color
+ maybe_color = parse_one_color(comp)
+ if maybe_color: color = maybe_color
+
+ return [color, background_color, bold, underline]
+
+
def parse_bool(val):
- val = val.lower()
- if val.startswith('f') or val.startswith('0'): return False
- if val.startswith('t') or val.startswith('1'): return True
- return bool(val)
+ val = val.lower()
+ if val.startswith('f') or val.startswith('0'): return False
+ if val.startswith('t') or val.startswith('1'): return True
+ return bool(val)
class FishVar:
- """ A class that represents a variable """
- def __init__(self, name, value):
- self.name = name
- self.value = value
- self.universal = False
- self.exported = False
-
- def get_json_obj(self):
- # Return an array(3): name, value, flags
- flags = []
- if self.universal: flags.append('universal')
- if self.exported: flags.append('exported')
- return [self.name, self.value, ', '.join(flags)]
+ """ A class that represents a variable """
+ def __init__(self, name, value):
+ self.name = name
+ self.value = value
+ self.universal = False
+ self.exported = False
+
+ def get_json_obj(self):
+ # Return an array(3): name, value, flags
+ flags = []
+ if self.universal: flags.append('universal')
+ if self.exported: flags.append('exported')
+ return [self.name, self.value, ', '.join(flags)]
class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
-
- def do_get_colors(self):
- "Look for fish_color_*"
- result = []
- remaining = set(['normal',
- 'error',
- 'command',
- 'end',
- 'param',
- 'comment',
- 'match',
- 'search_match',
- 'operator',
- 'escape',
- 'quote',
- 'redirection',
- 'valid_path',
- 'autosuggestion'
- ])
- out, err = run_fish_cmd('set -L')
- 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)]
- result.append([color_name, parse_color(color_value)])
- remaining.discard(color_name)
-
- # 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 x in remaining:
- result.append([x, parse_color('')])
-
- # Sort our result (by their keys)
- result.sort()
-
- return result
-
- def do_get_functions(self):
- out, err = run_fish_cmd('functions')
- out = out.strip()
-
- # Not sure why fish sometimes returns this with newlines
- if "\n" in out:
- return out.split('\n')
- else:
- return out.strip().split(', ')
-
- def do_get_variable_names(self, cmd):
- " Given a command like 'set -U' return all the variable names "
- out, err = run_fish_cmd(cmd)
- return out.split('\n')
-
- def do_get_variables(self):
- out, err = run_fish_cmd('set -L')
-
- # 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
-
- # Mark universal variables. L means don't abbreviate.
- for name in self.do_get_variable_names('set -nUL'):
- 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)]
-
- 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_colors(self):
+ # Looks for fish_color_*.
+ # Returns an array of lists [color_name, color_description, color_value]
+ result = []
+
+ # Make sure we return at least these
+ remaining = set(['normal',
+ 'error',
+ 'command',
+ 'end',
+ 'param',
+ 'comment',
+ 'match',
+ 'search_match',
+ 'operator',
+ 'escape',
+ 'quote',
+ 'redirection',
+ 'valid_path',
+ 'autosuggestion'
+ ])
+
+ # Here are our color descriptions
+ descriptions = {
+ 'normal': 'Default text',
+ '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'
+ }
+
+ out, err = run_fish_cmd('set -L')
+ 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)
+
+ # 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('')])
+
+ # Sort our result (by their keys)
+ result.sort()
+
+ return result
+
+ def do_get_functions(self):
+ out, err = run_fish_cmd('functions')
+ out = out.strip()
+
+ # Not sure why fish sometimes returns this with newlines
+ if "\n" in out:
+ return out.split('\n')
+ else:
+ return out.strip().split(', ')
+
+ def do_get_variable_names(self, cmd):
+ " Given a command like 'set -U' return all the variable names "
+ out, err = run_fish_cmd(cmd)
+ return out.split('\n')
+
+ def do_get_variables(self):
+ out, err = run_fish_cmd('set -L')
+
+ # 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
+
+ # Mark universal variables. L means don't abbreviate.
+ for name in self.do_get_variable_names('set -nUL'):
+ 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)]
+
+ 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):
- "Return the color with the given name, or the empty string if there is none"
- out, err = run_fish_cmd("echo -n $" + name)
- return out
-
- def do_set_color_for_variable(self, name, color, background_color, bold, underline):
- if not color: color = 'normal'
- "Sets a color for a fish color name, like 'autosuggestion'"
- command = 'set -U fish_color_' + name
- if color: command += ' ' + color
- if background_color: command += ' --background=' + background_color
- if bold: command += ' --bold'
- if underline: command += ' --underline'
-
- out, err = run_fish_cmd(command)
- return out
-
- def do_get_function(self, func_name):
- out, err = run_fish_cmd('functions ' + func_name)
- return out
-
- def do_GET(self):
- p = self.path
- if p == '/colors/':
- output = self.do_get_colors()
- elif p == '/functions/':
- output = self.do_get_functions()
- elif p == '/variables/':
- output = self.do_get_variables()
- elif p == '/history/':
- output = self.do_get_history()
- elif re.match(r"/color/(\w+)/", p):
- name = re.match(r"/color/(\w+)/", p).group(1)
- output = self.do_get_color_for_variable(name)
- else:
- return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
-
- # Return valid output
- self.send_response(200)
- self.send_header('Content-type','text/html')
- self.wfile.write('\n')
-
- # Output JSON
- json.dump(output, self.wfile)
-
- def do_POST(self):
- p = self.path
- ctype, pdict = cgi.parse_header(self.headers.getheader('content-type'))
- if ctype == 'multipart/form-data':
- postvars = cgi.parse_multipart(self.rfile, pdict)
- elif ctype == 'application/x-www-form-urlencoded':
- length = int(self.headers.getheader('content-length'))
- postvars = cgi.parse_qs(self.rfile.read(length), keep_blank_values=1)
- else:
- postvars = {}
-
- if p == '/set_color/':
- what = postvars.get('what')
- color = postvars.get('color')
- background_color = postvars.get('background_color')
- bold = postvars.get('bold')
- underline = postvars.get('underline')
- if what:
- # 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]))
- else:
- output = 'Bad request'
- elif p == '/get_function/':
- what = postvars.get('what')
- output = [self.do_get_function(what[0])]
- else:
- return SimpleHTTPServer.SimpleHTTPRequestHandler.do_POST(self)
-
- # Return valid output
- self.send_response(200)
- self.send_header('Content-type','text/html')
- self.wfile.write('\n')
-
- # Output JSON
- json.dump(output, self.wfile)
+ def do_get_color_for_variable(self, name):
+ "Return the color with the given name, or the empty string if there is none"
+ out, err = run_fish_cmd("echo -n $" + name)
+ return out
+
+ def do_set_color_for_variable(self, name, color, background_color, bold, underline):
+ if not color: color = 'normal'
+ "Sets a color for a fish color name, like 'autosuggestion'"
+ command = 'set -U fish_color_' + name
+ if color: command += ' ' + color
+ if background_color: command += ' --background=' + background_color
+ if bold: command += ' --bold'
+ if underline: command += ' --underline'
+
+ out, err = run_fish_cmd(command)
+ return out
+
+ def do_get_function(self, func_name):
+ out, err = run_fish_cmd('functions ' + func_name)
+ return out
+
+ def do_GET(self):
+ p = self.path
+ if p == '/colors/':
+ output = self.do_get_colors()
+ elif p == '/functions/':
+ output = self.do_get_functions()
+ elif p == '/variables/':
+ output = self.do_get_variables()
+ elif p == '/history/':
+ output = self.do_get_history()
+ elif re.match(r"/color/(\w+)/", p):
+ name = re.match(r"/color/(\w+)/", p).group(1)
+ output = self.do_get_color_for_variable(name)
+ else:
+ return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
+
+ # Return valid output
+ self.send_response(200)
+ self.send_header('Content-type','text/html')
+ self.wfile.write('\n')
+
+ # Output JSON
+ json.dump(output, self.wfile)
+
+ def do_POST(self):
+ p = self.path
+ ctype, pdict = cgi.parse_header(self.headers.getheader('content-type'))
+ if ctype == 'multipart/form-data':
+ postvars = cgi.parse_multipart(self.rfile, pdict)
+ elif ctype == 'application/x-www-form-urlencoded':
+ length = int(self.headers.getheader('content-length'))
+ postvars = cgi.parse_qs(self.rfile.read(length), keep_blank_values=1)
+ else:
+ postvars = {}
+
+ if p == '/set_color/':
+ what = postvars.get('what')
+ color = postvars.get('color')
+ background_color = postvars.get('background_color')
+ bold = postvars.get('bold')
+ underline = postvars.get('underline')
+ if what:
+ # 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]))
+ else:
+ output = 'Bad request'
+ elif p == '/get_function/':
+ what = postvars.get('what')
+ output = [self.do_get_function(what[0])]
+ else:
+ return SimpleHTTPServer.SimpleHTTPRequestHandler.do_POST(self)
+
+ # Return valid output
+ self.send_response(200)
+ self.send_header('Content-type','text/html')
+ self.wfile.write('\n')
+
+ # Output JSON
+ json.dump(output, self.wfile)
- def log_request(self, code='-', size='-'):
- """ Disable request logging """
- pass
+ def log_request(self, code='-', size='-'):
+ """ Disable request logging """
+ pass
# Make sure that the working directory is the one that contains the script server file,
# because the document root is the working directory
@@ -258,23 +284,23 @@ os.chdir(where)
# Try to find a suitable port
PORT = 8000
while PORT <= 9000:
- try:
- Handler = FishConfigHTTPRequestHandler
- httpd = SocketServer.TCPServer(("", PORT), Handler)
- # Success
- break;
- except socket.error:
- type, value = sys.exc_info()[:2]
- if 'Address already in use' not in value:
- break
- PORT += 1
+ try:
+ Handler = FishConfigHTTPRequestHandler
+ httpd = SocketServer.TCPServer(("", PORT), Handler)
+ # Success
+ break;
+ except socket.error:
+ type, value = sys.exc_info()[:2]
+ if 'Address already in use' not in value:
+ break
+ PORT += 1
if PORT > 9000:
- # Nobody say it
- print "Unable to find an open port between 8000 and 9000"
- sys.exit(-1)
+ # Nobody say it
+ print "Unable to find an open port between 8000 and 9000"
+ sys.exit(-1)
-
+
url = 'http://localhost:%d' % PORT
print "Web config started at '%s'. Hit enter to stop." % url
@@ -283,11 +309,11 @@ webbrowser.open(url)
# Select on stdin and httpd
stdin_no = sys.stdin.fileno()
while True:
- ready_read, _, _ = select.select([sys.stdin.fileno(), httpd.fileno()], [], [])
- if stdin_no in ready_read:
- print "Shutting down."
- # Consume the newline so it doesn't get printed by the caller
- sys.stdin.readline()
- break
- else:
- httpd.handle_request()
+ ready_read, _, _ = select.select([sys.stdin.fileno(), httpd.fileno()], [], [])
+ if stdin_no in ready_read:
+ print "Shutting down."
+ # Consume the newline so it doesn't get printed by the caller
+ sys.stdin.readline()
+ break
+ else:
+ httpd.handle_request()