CLI: Add find command and chat history sizing

This extends the Command Line Interpreter with two new features:
* Add command
    :find 'string'
  Only show log lines that match the given string, including
  newly incoming log lines.  Hit Ctrl+X to end the search.
  Ctrl+F works as a shortcut.
* Add command
    :history #
  Change the change history height to # lines.  This setting
  stays across sessions.
pull/841/merge
Steve Penrod 2017-09-14 10:57:40 -07:00 committed by Matthew D. Scholefield
parent 326939df35
commit 92b8b59cde
1 changed files with 92 additions and 33 deletions

View File

@ -60,6 +60,9 @@ filteredLog = []
default_log_filters = ["mouth.viseme"]
log_filters = list(default_log_filters)
log_files = []
find_str = None
cy_chat_area = 7 # default chat history height (in lines)
# Values used to display the audio meter
show_meter = True
@ -92,12 +95,15 @@ config_file = os.path.join(os.path.expanduser("~"), ".mycroft_cli.conf")
def load_settings():
global log_filters
global cy_chat_area
try:
with open(config_file, 'r') as f:
config = json.load(f)
if "filters" in config:
log_filters = config["filters"]
if "cy_chat_area" in config:
cy_chat_area = config["cy_chat_area"]
except:
pass
@ -105,6 +111,7 @@ def load_settings():
def save_settings():
config = {}
config["filters"] = log_filters
config["cy_chat_area"] = cy_chat_area
with open(config_file, 'w') as f:
json.dump(config, f)
@ -150,10 +157,14 @@ class LogMonitorThread(Thread):
# Allow user to filter log output
ignore = False
for filtered_text in log_filters:
if filtered_text in line:
if find_str:
if find_str not in line:
ignore = True
break
else:
for filtered_text in log_filters:
if filtered_text in line:
ignore = True
break
if ignore:
mergedLog.append(self.logid + line.strip())
@ -242,10 +253,17 @@ def rebuild_filtered_log():
for line in mergedLog:
# Apply filters
ignore = False
for filtered_text in log_filters:
if filtered_text in line:
if find_str and find_str != "":
# Searching log
if find_str not in line:
ignore = True
break
else:
# Apply filters
for filtered_text in log_filters:
if filtered_text in line:
ignore = True
break
if not ignore:
filteredLog.append(line)
@ -283,6 +301,7 @@ def handle_message(msg):
def init_screen():
global CLR_HEADING
global CLR_FIND
global CLR_CHAT_RESP
global CLR_CHAT_QUERY
global CLR_CMDLINE
@ -308,6 +327,7 @@ def init_screen():
CLR_HEADING = curses.color_pair(1)
CLR_CHAT_RESP = curses.color_pair(4)
CLR_CHAT_QUERY = curses.color_pair(7)
CLR_FIND = curses.color_pair(4)
CLR_CMDLINE = curses.color_pair(7)
CLR_INPUT = curses.color_pair(7)
CLR_LOG1 = curses.color_pair(3)
@ -331,7 +351,7 @@ def page_log(page_up):
draw_screen()
def draw_meter():
def draw_meter(height):
if not show_meter or meter_cur == -1:
return
@ -351,7 +371,6 @@ def draw_meter():
if meter_cur > meter_peak:
meter_peak = meter_cur + 1
height = 9 # curses.LINES/3
scale = meter_peak
if meter_peak > meter_thresh * 3:
scale = meter_thresh * 3
@ -417,7 +436,7 @@ def draw_screen():
# Display log output at the top
cLogs = len(filteredLog) + 1 # +1 for the '--end--'
cLogLinesToShow = curses.LINES - 13
cLogLinesToShow = curses.LINES - (cy_chat_area + 5) # 5 = headers+input
start = clamp(cLogs - cLogLinesToShow, 0, cLogs - 1) - log_line_offset
end = cLogs - log_line_offset
if start < 0:
@ -431,8 +450,18 @@ def draw_screen():
# adjust the line offset (prevents paging up too far)
log_line_offset = cLogs - end
scr.addstr(0, 0, "Log Output:" + " " * (curses.COLS - 31) + str(start) +
"-" + str(end) + " of " + str(cLogs), CLR_HEADING)
# Top header and line counts
if find_str:
scr.addstr(0, 0, "Search Results: ", CLR_HEADING)
scr.addstr(0, 16, find_str, CLR_FIND)
scr.addstr(0, 16+len(find_str), " ctrl+X to end" +
" " * (curses.COLS - 31 - 12 - len(find_str)) +
str(start) + "-" + str(end) + " of " + str(cLogs),
CLR_HEADING)
else:
scr.addstr(0, 0, "Log Output:" + " " * (curses.COLS - 31) +
str(start) + "-" + str(end) + " of " + str(cLogs),
CLR_HEADING)
scr.addstr(1, 0, "=" * (curses.COLS - 1), CLR_HEADING)
y = 2
len_line = 0
@ -478,34 +507,36 @@ def draw_screen():
y += 1
# Log legend in the lower-right
scr.addstr(curses.LINES - 10, curses.COLS / 2 + 2,
y_log_legend = curses.LINES - (3+cy_chat_area)
scr.addstr(y_log_legend, curses.COLS / 2 + 2,
make_titlebar("Log Output Legend", curses.COLS / 2 - 2),
CLR_HEADING)
scr.addstr(curses.LINES - 9, curses.COLS / 2 + 2,
scr.addstr(y_log_legend+1, curses.COLS / 2 + 2,
"DEBUG output",
CLR_LOG_DEBUG)
scr.addstr(curses.LINES - 8, curses.COLS / 2 + 2,
scr.addstr(y_log_legend+2, curses.COLS / 2 + 2,
os.path.basename(log_files[0]) + ", other",
CLR_LOG1)
if len(log_files) > 1:
scr.addstr(curses.LINES - 7, curses.COLS / 2 + 2,
scr.addstr(y_log_legend+3, curses.COLS / 2 + 2,
os.path.basename(log_files[1]), CLR_LOG2)
# Meter
y_meter = y_log_legend
if show_meter:
scr.addstr(curses.LINES - 10, curses.COLS - 14, " Mic Level ",
scr.addstr(y_meter, curses.COLS - 14, " Mic Level ",
CLR_HEADING)
# History log in the middle
y_chat_history = curses.LINES - (3+cy_chat_area)
chat_width = curses.COLS / 2 - 2
chat_height = 7
chat_out = []
scr.addstr(curses.LINES - 10, 0, make_titlebar("History", chat_width),
scr.addstr(y_chat_history, 0, make_titlebar("History", chat_width),
CLR_HEADING)
# Build a nicely wrapped version of the chat log
idx_chat = len(chat) - 1
while len(chat_out) < chat_height and idx_chat >= 0:
while len(chat_out) < cy_chat_area and idx_chat >= 0:
if chat[idx_chat][0] == '>':
wrapper = textwrap.TextWrapper(initial_indent="",
subsequent_indent=" ",
@ -515,14 +546,14 @@ def draw_screen():
chatlines = wrapper.wrap(chat[idx_chat])
for txt in reversed(chatlines):
if len(chat_out) >= chat_height:
if len(chat_out) >= cy_chat_area:
break
chat_out.insert(0, txt)
idx_chat -= 1
# Output the chat
y = curses.LINES - 9
y = curses.LINES - (2+cy_chat_area)
for txt in chat_out:
if txt.startswith(">> ") or txt.startswith(" "):
clr = CLR_CHAT_RESP
@ -545,7 +576,7 @@ def draw_screen():
CLR_HEADING)
scr.addstr(curses.LINES - 1, 0, ">", CLR_HEADING)
draw_meter()
draw_meter(cy_chat_area+2)
scr.addstr(curses.LINES - 1, 2, l, CLR_INPUT)
scr.refresh()
@ -583,6 +614,8 @@ def show_help():
scr.addstr(15, 0, ":filter [remove] 'str' adds or removes a log filter")
scr.addstr(16, 0, ":filter (clear|reset) reset filters")
scr.addstr(17, 0, ":filter (show|list) display current filters")
scr.addstr(18, 0, ":history (# lines) set number of history lines")
scr.addstr(19, 0, ":find 'str' show logs containing 'str'")
scr.addstr(curses.LINES - 1, 0, center(23) + "Press any key to return",
CLR_HEADING)
@ -603,10 +636,26 @@ def center(str_len):
##############################################################################
# Main UI lopo
def _get_cmd_param(cmd):
# Returns parameter to a command. Will de-quote.
# Ex: find 'abc def' returns: abc def
# find abc def returns: abc def
cmd = cmd.strip()
last_char = cmd[-1]
if last_char == '"' or last_char == "'":
parts = cmd.split(last_char)
return parts[-2]
else:
parts = cmd.split(" ")
return parts[-1]
def handle_cmd(cmd):
global show_meter
global log_filters
global mergedLog
global cy_chat_area
global find_str
if "show" in cmd and "log" in cmd:
pass
@ -620,6 +669,9 @@ def handle_cmd(cmd):
show_meter = False
elif "show" in cmd or "on" in cmd:
show_meter = True
elif "find" in cmd:
find_str = _get_cmd_param(cmd)
rebuild_filtered_log()
elif "filter" in cmd:
if "show" in cmd or "list" in cmd:
# display active filters
@ -630,22 +682,21 @@ def handle_cmd(cmd):
log_filters = list(default_log_filters)
else:
# extract last word(s)
cmd = cmd.strip()
last_char = cmd[-1]
if last_char == '"' or last_char == "'":
parts = cmd.split(last_char)
word = parts[-2]
else:
parts = cmd.split(" ")
word = parts[-1]
param = get_cmd_param(cmd)
if "remove" in cmd and word in log_filters:
log_filters.remove(word)
if "remove" in cmd and param in log_filters:
log_filters.remove(param)
else:
log_filters.append(word)
log_filters.append(param)
rebuild_filtered_log()
add_log_message("Filters: " + str(log_filters))
elif "history" in cmd:
# extract last word(s)
lines = int(_get_cmd_param(cmd))
if (lines < 1):
lines = 1
cy_chat_area = lines
# TODO: More commands
# elif "find" in cmd:
@ -659,6 +710,7 @@ def gui_main(stdscr):
global line
global log_line_lr_scroll
global longest_visible_line
global find_str
scr = stdscr
init_screen()
@ -768,6 +820,13 @@ def gui_main(stdscr):
elif c == curses.KEY_BACKSPACE or c == 127:
# Backspace to erase a character in the utterance
line = line[:-1]
elif c == 6: # Ctrl+F
line = ":find "
elif c == 24: # Ctrl+X
if find_str:
# End the find session
find_str = None
rebuild_filtered_log()
elif curses.ascii.isascii(c):
# Accept typed character in the utterance
line += chr(c)