Skip to content

Commit

Permalink
Implement command history in console
Browse files Browse the repository at this point in the history
  • Loading branch information
ObaraEmmanuel committed Apr 7, 2024
1 parent 2356157 commit 3dba3c5
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 10 deletions.
94 changes: 91 additions & 3 deletions studio/debugtools/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class ConsoleText(Text):
A Text widget which handles some application logic,
e.g. having a line of input at the end with everything else being uneditable
"""
PROMPT = ">>> "

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand Down Expand Up @@ -82,7 +83,7 @@ def prompt(self):
"""Insert a prompt"""
self.mark_set("prompt_end", 'end-1c')
self.mark_gravity("prompt_end", tk.LEFT)
self.write(">>> ", "prompt", foreground=self.style.colors["secondary1"])
self.write(self.PROMPT, "prompt", foreground=self.style.colors["secondary1"])
self.mark_gravity("prompt_end", tk.RIGHT)

def commit_all(self):
Expand Down Expand Up @@ -178,14 +179,20 @@ def __init__(self, parent, exit_callback, debugger):

self.text = ConsoleText(self.console_frame, wrap=tk.WORD, font=("Consolas", 12))
self.text.pack(fill=tk.BOTH, expand=True)
self.text.bind("<Up>", self._on_up)
self.text.bind("<Down>", self._on_down)
self.text.bind("<Left>", self._on_left)
self.console_frame.set_child(self.text)
self.console_frame.show_scroll(AutoScroll.Y)

# make the enter key call the self.enter function
self.text.bind("<Return>", self.enter)
self.prompt_flag = True
self.mark_input_flag = True
self.command_running = False
self.stdin_reading = False
self.history_index = -1
self.last_typed = ""
self.exit_callback = exit_callback

self.stdout = Pipe()
Expand Down Expand Up @@ -231,6 +238,73 @@ def clear(self):
self.stderr.clear()
self.prompt()

def mark_input(self):
self.mark_input_flag = True

def _mark_input(self):
self.text.mark_set("input", "end-1c")
self.text.mark_gravity("input", tk.LEFT)
self.mark_input_flag = False

def is_valid_input_pos(self, index):
if isinstance(index, str):
index = self._idx(index)
inpt = self._idx("input")
return index >= inpt

def _idx(self, tag):
index = self.text.index(tag)
return tuple(map(int, index.split('.')))

def _on_up(self, _):
cur = self._idx(tk.INSERT)
ins = (cur[0] - 1, cur[1])

if not self.is_valid_input_pos(ins) and self.is_valid_input_pos(cur):
if self.command_running:
return 'break'

history = self.debugger.pref.get("console::history")
current = self.text.get("input", "end-1c")

if self.history_index == -1 or current != history[self.history_index]:
self.last_typed = current
self.history_index = -1

if self.history_index + 1 < len(history):
self.text.delete("input", tk.END)
self.history_index += 1
self.text.insert("input", history[self.history_index])

return 'break'

def _on_down(self, _):
if self._idx(tk.END)[0] - 1 == self._idx(tk.INSERT)[0] and self.is_valid_input_pos(tk.INSERT):
if self.command_running:
return 'break'
history = self.debugger.pref.get("console::history")

if self.history_index > 0:
self.history_index -= 1
self.text.delete("input", tk.END)
self.text.insert("input", history[self.history_index])
elif self.history_index == 0:
self.text.delete("input", tk.END)
self.text.insert("input", self.last_typed)
self.history_index -= 1

def _on_left(self, _):
ins = self._idx(tk.INSERT)
ins = (ins[0], ins[1] - 1)

if not self.is_valid_input_pos(ins):
return 'break'

def _on_tap(self, event):
ins = self._idx(tk.CURRENT)
if not self.is_valid_input_pos(ins):
return 'break'

def prompt(self):
"""Add a '>>> ' to the console"""
self.prompt_flag = True
Expand All @@ -241,8 +315,13 @@ def read_from_pipe(self, pipe: Pipe, tag_name, **kwargs):
# write the >>>
if self.prompt_flag and not self.command_running:
self.text.prompt()
self._mark_input()
self.prompt_flag = False

# mark the start of the input area
if self.mark_input_flag:
self._mark_input()

# get data from buffer
string_parts = []
while not pipe.buffer.empty():
Expand All @@ -261,19 +340,22 @@ def read_from_pipe(self, pipe: Pipe, tag_name, **kwargs):

def enter(self, _):
"""The <Return> key press handler"""
if self.text.index(tk.INSERT) != self.text.index(tk.END):
self.text.mark_set(tk.INSERT, tk.END)

if self.stdin_reading:
# if stdin requested, then put data in stdin instead of running a new command
line = self.text.consume_last_line()
line = line + '\n'
line = line.lstrip('\n')
self.debugger.transmit(
Message("CONSOLE", payload={"tag": "stdin", "meth": "_write", "args": [line]}), response=True
)
return

# don't run multiple commands simultaneously
if self.command_running:
return
return 'break'

# get the command text
command = self.text.read_last_line()
Expand All @@ -289,15 +371,21 @@ def enter(self, _):
self.text.consume_last_line()
self.prompt()
# limit the traceback to avoid exposing the underlying compilation error
traceback.print_exc(limit=0)
self.debugger.transmit(Message(
"CONSOLE", payload={"tag": "stderr", "meth": "write", "args": [traceback.format_exc(limit=0)]}
), response=True)
return

# if it is a complete command
if compiled:
# consume the line and run the command
self.text.consume_last_line()
self.prompt()
self.mark_input()
self.command_running = True
self.debugger.transmit(Message(
"HOOK", payload={"meth": "console_run"}
), response=True)
if command:
self.debugger.pref.update_console_history(command)
self.history_index = -1
6 changes: 6 additions & 0 deletions studio/debugtools/defs.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ def __getitem__(self, item):
def __setitem__(self, key, value):
return self._call("__setitem__", key, value)

def get_prop(self, prop):
prop = self[prop]
if isinstance(prop, (tuple, list)):
prop = " ".join(map(str, prop))
return prop

def keys(self):
return self._call("keys")

Expand Down
10 changes: 5 additions & 5 deletions studio/debugtools/hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,6 @@ def __init__(self, path=None):
self._allow_hover = False
self._handle = None
self.last_compiled = None
self.debugger_api = DebuggerAPI(self)
self.shell = code.InteractiveConsole({"debugger": self.debugger_api})
self._stream_clients = []
self._server_clients = []
self.selection = []
self.pipes = {
"stdout": RemotePipe(self, "stdout"),
"stderr": RemotePipe(self, "stderr"),
Expand All @@ -112,6 +107,11 @@ def __init__(self, path=None):
sys.stdout = self.pipes["stdout"]
sys.stderr = self.pipes["stderr"]
sys.stdin = self.pipes["stdin"]
self.debugger_api = DebuggerAPI(self)
self.shell = code.InteractiveConsole({"debugger": self.debugger_api})
self._stream_clients = []
self._server_clients = []
self.selection = []
atexit.register(self.terminate)

@property
Expand Down
8 changes: 7 additions & 1 deletion studio/debugtools/layouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@ class BaseLayout:
defs = {}
name = ''

@staticmethod
def clean(value):
if isinstance(value, (list, tuple)):
return " ".join(map(str, value))
return value

@classmethod
def get_def(cls, widget):
info = cls.configure(widget)
# Ensure we use the dynamic definitions
definition = dict(cls.defs)
for key in definition:
# will throw a key error if a definition value is not found in info
definition[key]["value"] = info[key]
definition[key]["value"] = cls.clean(info[key])
return definition

@classmethod
Expand Down
14 changes: 14 additions & 0 deletions studio/debugtools/preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
"geometry": "400x500",
},
"resource": studio.preferences.defaults["resource"],
"console": {
"history_max": 1000,
"history": []
},
"locale": {
"language": "en"
}
Expand All @@ -17,3 +21,13 @@ class Preferences(SharedPreferences):
@classmethod
def acquire(cls):
return cls("formation-debugger", "hoverset", "config", defaults)

def update_console_history(self, command):
history = self.get("console::history")
max_hist = self.get("console::history_max")
if command in history:
history.remove(command)

history = history[: max_hist - 1]
history.insert(0, command)
self.set("console::history", history)
5 changes: 4 additions & 1 deletion studio/debugtools/style_pane.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ def get_definition(self):
def _get_prop(self, prop, widget):
layout = layouts.get_layout(widget)
if layout:
return layout.configure(widget)[prop]
prop = layout.configure(widget)[prop]
if isinstance(prop, (list, tuple)):
return " ".join(map(str, prop))
return prop

def _set_prop(self, prop, value, widget):
layout = layouts.get_layout(widget)
Expand Down

0 comments on commit 3dba3c5

Please sign in to comment.