Coding style.

- Since there is only one display, appearance_changed_event can be
  a module level global in fill3.
- Remove all the appearance_changed_event plumbing.
This commit is contained in:
Andrew Hamilton 2021-11-29 15:57:16 +10:00
parent 71b9da128b
commit 4150a9a250
5 changed files with 55 additions and 76 deletions

View file

@ -340,7 +340,7 @@ class Summary:
if y >= len(self._entries): if y >= len(self._entries):
self._cursor_position = (x, len(self._entries) - 1) self._cursor_position = (x, len(self._entries) - 1)
async def sync_with_filesystem(self, appearance_changed_event, log=None): async def sync_with_filesystem(self, log=None):
start_time = time.time() start_time = time.time()
cache = {} cache = {}
log.log_message("Started loading summary…") log.log_message("Started loading summary…")
@ -350,7 +350,7 @@ class Summary:
await asyncio.sleep(0) await asyncio.sleep(0)
self.add_entry(entry) self.add_entry(entry)
if index % 1000 == 0: if index % 1000 == 0:
appearance_changed_event.set() fill3.APPEARANCE_CHANGED_EVENT.set()
cache[entry.path] = entry.change_time cache[entry.path] = entry.change_time
duration = time.time() - start_time duration = time.time() - start_time
log.log_message(f"Finished loading summary. {round(duration, 2)} secs") log.log_message(f"Finished loading summary. {round(duration, 2)} secs")
@ -371,7 +371,7 @@ class Summary:
entry.change_time = change_time entry.change_time = change_time
else: else:
self.on_file_added(path) self.on_file_added(path)
appearance_changed_event.set() fill3.APPEARANCE_CHANGED_EVENT.set()
for path in cache.keys() - all_paths: for path in cache.keys() - all_paths:
await asyncio.sleep(0) await asyncio.sleep(0)
self.on_file_deleted(path) self.on_file_deleted(path)
@ -565,16 +565,10 @@ class Log:
_GREY_BOLD_STYLE = termstr.CharStyle(termstr.Color.grey_100, is_bold=True) _GREY_BOLD_STYLE = termstr.CharStyle(termstr.Color.grey_100, is_bold=True)
_GREEN_STYLE = termstr.CharStyle(termstr.Color.lime) _GREEN_STYLE = termstr.CharStyle(termstr.Color.lime)
def __init__(self, appearance_changed_event): def __init__(self):
self._appearance_changed_event = appearance_changed_event
self.lines = [] self.lines = []
self._appearance = None self._appearance = None
def __getstate__(self):
state = self.__dict__.copy()
state["_appearance_changed_event"] = None
return state
def log_message(self, message, timestamp=None, char_style=None): def log_message(self, message, timestamp=None, char_style=None):
if isinstance(message, list): if isinstance(message, list):
message = [part[1] if isinstance(part, tuple) else part for part in message] message = [part[1] if isinstance(part, tuple) else part for part in message]
@ -588,7 +582,7 @@ class Log:
return return
self.lines.append(line) self.lines.append(line)
self._appearance = None self._appearance = None
self._appearance_changed_event.set() fill3.APPEARANCE_CHANGED_EVENT.set()
def log_command(self, message, timestamp=None): def log_command(self, message, timestamp=None):
self.log_message(message, char_style=Log._GREEN_STYLE) self.log_message(message, char_style=Log._GREEN_STYLE)
@ -631,20 +625,20 @@ class Help:
def _exit_help(self): def _exit_help(self):
self.screen._is_help_visible = False self.screen._is_help_visible = False
def on_mouse_input(self, term_code, appearance_changed_event): def on_mouse_input(self, term_code):
event = terminal.decode_mouse_input(term_code) event = terminal.decode_mouse_input(term_code)
if event[1] == terminal.WHEEL_UP_MOUSE: if event[1] == terminal.WHEEL_UP_MOUSE:
self.view.portal.scroll_up() self.view.portal.scroll_up()
appearance_changed_event.set() fiil3.APPEARANCE_CHANGED_EVENT.set()
elif event[1] == terminal.WHEEL_DOWN_MOUSE: elif event[1] == terminal.WHEEL_DOWN_MOUSE:
self.view.portal.scroll_down() self.view.portal.scroll_down()
appearance_changed_event.set() fill3.APPEARANCE_CHANGED_EVENT.set()
def on_keyboard_input(self, term_code, appearance_changed_event): def on_keyboard_input(self, term_code):
action = self.key_map.get(term_code) or self.key_map.get(term_code.lower()) action = self.key_map.get(term_code) or self.key_map.get(term_code.lower())
if action is not None: if action is not None:
action() action()
appearance_changed_event.set() fill3.APPEARANCE_CHANGED_EVENT.set()
def appearance(self, dimensions): def appearance(self, dimensions):
return self.widget.appearance(dimensions) return self.widget.appearance(dimensions)
@ -663,10 +657,9 @@ class Listing:
class Screen: class Screen:
def __init__(self, summary, log, appearance_changed_event, main_loop): def __init__(self, summary, log, main_loop):
self._summary = summary self._summary = summary
self._log = log self._log = log
self._appearance_changed_event = appearance_changed_event
self._main_loop = main_loop self._main_loop = main_loop
self._is_summary_focused = True self._is_summary_focused = True
self.workers = None self.workers = None
@ -679,7 +672,6 @@ class Screen:
def __getstate__(self): def __getstate__(self):
state = self.__dict__.copy() state = self.__dict__.copy()
state["_appearance_changed_event"] = None
state["_main_loop"] = None state["_main_loop"] = None
state["workers"] = None state["workers"] = None
return state return state
@ -690,8 +682,7 @@ class Screen:
worker_ = worker.Worker(is_being_tested, compression) worker_ = worker.Worker(is_being_tested, compression)
workers.append(worker_) workers.append(worker_)
future = worker_.job_runner(self, self._summary, self._log, future = worker_.job_runner(self, self._summary, self._log,
self._summary._jobs_added_event, self._summary._jobs_added_event)
self._appearance_changed_event)
worker_.future = future worker_.future = future
self.workers = workers self.workers = workers
@ -886,7 +877,7 @@ class Screen:
def on_mouse_input(self, term_code): def on_mouse_input(self, term_code):
if self._is_help_visible: if self._is_help_visible:
self._help_widget.on_mouse_input(term_code, self._appearance_changed_event) self._help_widget.on_mouse_input(term_code)
return return
event = terminal.decode_mouse_input(term_code) event = terminal.decode_mouse_input(term_code)
if event[0] not in [terminal.PRESS_MOUSE, terminal.DRAG_MOUSE]: if event[0] not in [terminal.PRESS_MOUSE, terminal.DRAG_MOUSE]:
@ -912,16 +903,16 @@ class Screen:
self._select_entry_at_position( self._select_entry_at_position(
x, y, view_width, view_height) x, y, view_width, view_height)
self._last_mouse_position = x, y self._last_mouse_position = x, y
self._appearance_changed_event.set() fill3.APPEARANCE_CHANGED_EVENT.set()
def on_keyboard_input(self, term_code): def on_keyboard_input(self, term_code):
if self._is_help_visible: if self._is_help_visible:
self._help_widget.on_keyboard_input(term_code, self._appearance_changed_event) self._help_widget.on_keyboard_input(term_code)
return return
action = Screen._KEY_MAP.get(term_code) or Screen._KEY_MAP.get(term_code.lower()) action = Screen._KEY_MAP.get(term_code) or Screen._KEY_MAP.get(term_code.lower())
if action is not None: if action is not None:
action(self) action(self)
self._appearance_changed_event.set() fill3.APPEARANCE_CHANGED_EVENT.set()
def _fix_listing(self): def _fix_listing(self):
widget = self._summary.get_selection() widget = self._summary.get_selection()
@ -999,29 +990,27 @@ def setup_inotify(root_path, loop, on_filesystem_event, exclude_filter):
return pyinotify.AsyncioNotifier(watch_manager, loop, callback=lambda notifier: None) return pyinotify.AsyncioNotifier(watch_manager, loop, callback=lambda notifier: None)
def load_state(pickle_path, jobs_added_event, appearance_changed_event, root_path, loop): def load_state(pickle_path, jobs_added_event, root_path, loop):
is_first_run = True is_first_run = True
try: try:
with gzip.open(pickle_path, "rb") as file_: with gzip.open(pickle_path, "rb") as file_:
screen = pickle.load(file_) screen = pickle.load(file_)
except (FileNotFoundError, AttributeError): except (FileNotFoundError, AttributeError):
summary = Summary(root_path, jobs_added_event) summary = Summary(root_path, jobs_added_event)
log = Log(appearance_changed_event) log = Log()
screen = Screen(summary, log, appearance_changed_event, loop) screen = Screen(summary, log, loop)
else: else:
is_first_run = False is_first_run = False
screen._appearance_changed_event = appearance_changed_event
screen._main_loop = loop screen._main_loop = loop
summary = screen._summary summary = screen._summary
summary._jobs_added_event = jobs_added_event summary._jobs_added_event = jobs_added_event
summary._root_path = root_path summary._root_path = root_path
summary.clear_running() summary.clear_running()
log = screen._log log = screen._log
log._appearance_changed_event = appearance_changed_event
return summary, screen, log, is_first_run return summary, screen, log, is_first_run
def on_filesystem_event(event, summary, root_path, appearance_changed_event): def on_filesystem_event(event, summary, root_path):
path = list(fix_paths(root_path, [event.pathname]))[0] path = list(fix_paths(root_path, [event.pathname]))[0]
if is_path_excluded(path[2:]): if is_path_excluded(path[2:]):
return return
@ -1038,7 +1027,7 @@ def on_filesystem_event(event, summary, root_path, appearance_changed_event):
except Exception: except Exception:
tools.log_error() tools.log_error()
raise KeyboardInterrupt raise KeyboardInterrupt
appearance_changed_event.set() fill3.APPEARANCE_CHANGED_EVENT.set()
def main(root_path, loop, worker_count=None, editor_command=None, theme=None, def main(root_path, loop, worker_count=None, editor_command=None, theme=None,
@ -1052,15 +1041,13 @@ def main(root_path, loop, worker_count=None, editor_command=None, theme=None,
os.environ["PYGMENT_STYLE"] = theme os.environ["PYGMENT_STYLE"] = theme
pickle_path = os.path.join(tools.CACHE_PATH, "summary.pickle") pickle_path = os.path.join(tools.CACHE_PATH, "summary.pickle")
jobs_added_event = asyncio.Event() jobs_added_event = asyncio.Event()
appearance_changed_event = asyncio.Event() summary, screen, log, is_first_run = load_state(pickle_path, jobs_added_event, root_path, loop)
summary, screen, log, is_first_run = load_state(pickle_path, jobs_added_event,
appearance_changed_event, root_path, loop)
screen.editor_command = editor_command screen.editor_command = editor_command
log.log_message("Program started.") log.log_message("Program started.")
jobs_added_event.set() jobs_added_event.set()
def callback(event): def callback(event):
on_filesystem_event(event, summary, root_path, appearance_changed_event) on_filesystem_event(event, summary, root_path)
notifier = setup_inotify(root_path, loop, callback, is_path_excluded) notifier = setup_inotify(root_path, loop, callback, is_path_excluded)
try: try:
log.log_message(f"Starting workers ({worker_count}) …") log.log_message(f"Starting workers ({worker_count}) …")
@ -1071,11 +1058,11 @@ def main(root_path, loop, worker_count=None, editor_command=None, theme=None,
time.sleep(0.05) time.sleep(0.05)
screen.stop_workers() screen.stop_workers()
loop.stop() loop.stop()
loop.create_task(summary.sync_with_filesystem(appearance_changed_event, log)) loop.create_task(summary.sync_with_filesystem(log))
for worker_ in screen.workers: for worker_ in screen.workers:
loop.create_task(worker_.future) loop.create_task(worker_.future)
if sys.stdout.isatty(): if sys.stdout.isatty():
with fill3.context(loop, appearance_changed_event, screen, exit_loop=exit_loop): with fill3.context(loop, screen, exit_loop=exit_loop):
loop.run_forever() loop.run_forever()
log.log_message("Program stopped.") log.log_message("Program stopped.")
else: else:

View file

@ -559,18 +559,18 @@ class Result:
def is_completed(self): def is_completed(self):
return self.status in Result.COMPLETED_STATUSES return self.status in Result.COMPLETED_STATUSES
async def run(self, log, appearance_changed_event, runner): async def run(self, log, runner):
tool_name = tool_name_colored(self.tool, self.path) tool_name = tool_name_colored(self.tool, self.path)
path = path_colored(self.path) path = path_colored(self.path)
log.log_message(["Running ", tool_name, " on ", path, ""]) log.log_message(["Running ", tool_name, " on ", path, ""])
self.set_status(Status.running) self.set_status(Status.running)
appearance_changed_event.set() fill3.APPEARANCE_CHANGED_EVENT.set()
start_time = time.time() start_time = time.time()
new_status = await runner.run_tool(self.path, self.tool) new_status = await runner.run_tool(self.path, self.tool)
Result.result.fget.evict(self) Result.result.fget.evict(self)
end_time = time.time() end_time = time.time()
self.set_status(new_status) self.set_status(new_status)
appearance_changed_event.set() fill3.APPEARANCE_CHANGED_EVENT.set()
log.log_message(["Finished running ", tool_name, " on ", path, ". ", log.log_message(["Finished running ", tool_name, " on ", path, ". ",
STATUS_TO_TERMSTR[new_status], f" {round(end_time - start_time, 2)} secs"]) STATUS_TO_TERMSTR[new_status], f" {round(end_time - start_time, 2)} secs"])

View file

@ -45,7 +45,7 @@ class Worker:
break break
return tools.Status(int(data)) return tools.Status(int(data))
async def job_runner(self, screen, summary, log, jobs_added_event, appearance_changed_event): async def job_runner(self, screen, summary, log, jobs_added_event):
await self.create_process() await self.create_process()
while True: while True:
await jobs_added_event.wait() await jobs_added_event.wait()
@ -55,7 +55,7 @@ class Worker:
except StopAsyncIteration: except StopAsyncIteration:
self.result = None self.result = None
break break
await self.result.run(log, appearance_changed_event, self) await self.result.run(log, self)
self.result.compression = self.compression self.result.compression = self.compression
Worker.unsaved_jobs_total += 1 Worker.unsaved_jobs_total += 1
if Worker.unsaved_jobs_total == 5000 and summary.is_loaded: if Worker.unsaved_jobs_total == 5000 and summary.is_loaded:

View file

@ -50,11 +50,9 @@ class ScreenWidgetTestCase(unittest.TestCase):
foo_path = os.path.join(project_dir, "foo.py") foo_path = os.path.join(project_dir, "foo.py")
_touch(foo_path) _touch(foo_path)
jobs_added_event = asyncio.Event() jobs_added_event = asyncio.Event()
appearance_changed_event = asyncio.Event()
summary = __main__.Summary(project_dir, jobs_added_event) summary = __main__.Summary(project_dir, jobs_added_event)
log = __main__.Log(appearance_changed_event) log = __main__.Log()
self.main_widget = __main__.Screen( self.main_widget = __main__.Screen(summary, log, _MockMainLoop())
summary, log, appearance_changed_event, _MockMainLoop())
def tearDown(self): def tearDown(self):
shutil.rmtree(self.temp_dir) shutil.rmtree(self.temp_dir)
@ -123,20 +121,17 @@ class SummarySyncWithFilesystemTestCase(unittest.TestCase):
self.bar_path = os.path.join(self.temp_dir, "bar.md") self.bar_path = os.path.join(self.temp_dir, "bar.md")
self.zoo_path = os.path.join(self.temp_dir, "zoo.html") self.zoo_path = os.path.join(self.temp_dir, "zoo.html")
self.jobs_added_event = asyncio.Event() self.jobs_added_event = asyncio.Event()
self.appearance_changed_event = asyncio.Event()
self.summary = __main__.Summary(self.temp_dir, self.jobs_added_event) self.summary = __main__.Summary(self.temp_dir, self.jobs_added_event)
self.loop = asyncio.new_event_loop() self.loop = asyncio.new_event_loop()
def callback(event): def callback(event):
__main__.on_filesystem_event(event, self.summary, self.temp_dir, __main__.on_filesystem_event(event, self.summary, self.temp_dir)
self.appearance_changed_event)
__main__.setup_inotify(self.temp_dir, self.loop, callback, __main__.setup_inotify(self.temp_dir, self.loop, callback,
__main__.is_path_excluded) __main__.is_path_excluded)
_touch(self.foo_path) _touch(self.foo_path)
_touch(self.bar_path) _touch(self.bar_path)
self.log = __main__.Log(self.appearance_changed_event) self.log = __main__.Log()
self.loop.run_until_complete(self.summary.sync_with_filesystem( self.loop.run_until_complete(self.summary.sync_with_filesystem(self.log))
self.appearance_changed_event, self.log))
self.jobs_added_event.clear() self.jobs_added_event.clear()
def tearDown(self): def tearDown(self):
@ -188,9 +183,8 @@ class SummarySyncWithFilesystemTestCase(unittest.TestCase):
baz_path = os.path.join(self.temp_dir, "baz") baz_path = os.path.join(self.temp_dir, "baz")
os.symlink(self.foo_path, baz_path) os.symlink(self.foo_path, baz_path)
os.link(self.foo_path, self.zoo_path) os.link(self.foo_path, self.zoo_path)
log = __main__.Log(self.appearance_changed_event) log = __main__.Log()
self.loop.run_until_complete(self.summary.sync_with_filesystem( self.loop.run_until_complete(self.summary.sync_with_filesystem(log))
self.appearance_changed_event, log))
self._assert_paths(["./bar.md", "./baz", "./foo", "./zoo.html"]) self._assert_paths(["./bar.md", "./baz", "./foo", "./zoo.html"])
self.assertTrue(id(self.summary._entries[1]) != # baz self.assertTrue(id(self.summary._entries[1]) != # baz
id(self.summary._entries[2])) # foo id(self.summary._entries[2])) # foo

View file

@ -406,30 +406,31 @@ class Fixed:
########################## ##########################
_last_appearance = [] APPEARANCE_CHANGED_EVENT = asyncio.Event()
_LAST_APPEARANCE = []
def draw_screen(widget): def draw_screen(widget):
global _last_appearance global _LAST_APPEARANCE
appearance = widget.appearance(os.get_terminal_size()) appearance = widget.appearance(os.get_terminal_size())
print(terminal.move(0, 0), *appearance, sep="", end="", flush=True) print(terminal.move(0, 0), *appearance, sep="", end="", flush=True)
_last_appearance = appearance _LAST_APPEARANCE = appearance
def patch_screen(widget): def patch_screen(widget):
global _last_appearance global _LAST_APPEARANCE
appearance = widget.appearance(os.get_terminal_size()) appearance = widget.appearance(os.get_terminal_size())
zip_func = (itertools.zip_longest if len(appearance) > len(_last_appearance) else zip) zip_func = (itertools.zip_longest if len(appearance) > len(_LAST_APPEARANCE) else zip)
changed_lines = (str(terminal.move(0, row_index)) + line for row_index, (line, old_line) changed_lines = (str(terminal.move(0, row_index)) + line for row_index, (line, old_line)
in enumerate(zip_func(appearance, _last_appearance)) if line != old_line) in enumerate(zip_func(appearance, _LAST_APPEARANCE)) if line != old_line)
print(*changed_lines, sep="", end="", flush=True) print(*changed_lines, sep="", end="", flush=True)
_last_appearance = appearance _LAST_APPEARANCE = appearance
async def update_screen(screen_widget, appearance_changed_event): async def update_screen(screen_widget):
while True: while True:
await appearance_changed_event.wait() await APPEARANCE_CHANGED_EVENT.wait()
appearance_changed_event.clear() APPEARANCE_CHANGED_EVENT.clear()
patch_screen(screen_widget) patch_screen(screen_widget)
await asyncio.sleep(0.01) await asyncio.sleep(0.01)
@ -453,8 +454,8 @@ def signal_handler(loop, signal_, func):
@contextlib.contextmanager @contextlib.contextmanager
def context(loop, appearance_changed_event, screen_widget, exit_loop=None): def context(loop, screen_widget, exit_loop=None):
appearance_changed_event.set() APPEARANCE_CHANGED_EVENT.set()
if exit_loop is None: if exit_loop is None:
exit_loop = loop.stop exit_loop = loop.stop
with (signal_handler(loop, signal.SIGWINCH, lambda: draw_screen(screen_widget)), with (signal_handler(loop, signal.SIGWINCH, lambda: draw_screen(screen_widget)),
@ -462,7 +463,7 @@ def context(loop, appearance_changed_event, screen_widget, exit_loop=None):
signal_handler(loop, signal.SIGTERM, exit_loop), terminal.alternate_buffer(), signal_handler(loop, signal.SIGTERM, exit_loop), terminal.alternate_buffer(),
terminal.interactive(), terminal.mouse_tracking()): terminal.interactive(), terminal.mouse_tracking()):
update_task = loop.create_task( update_task = loop.create_task(
update_screen(screen_widget, appearance_changed_event)) update_screen(screen_widget))
try: try:
loop.add_reader(sys.stdin, on_terminal_input, screen_widget) loop.add_reader(sys.stdin, on_terminal_input, screen_widget)
try: try:
@ -478,8 +479,7 @@ def context(loop, appearance_changed_event, screen_widget, exit_loop=None):
class _Screen: class _Screen:
def __init__(self, appearance_changed_event): def __init__(self):
self._appearance_changed_event = appearance_changed_event
self.content = Filler(Text("Hello World")) self.content = Filler(Text("Hello World"))
def appearance(self, dimensions): def appearance(self, dimensions):
@ -490,19 +490,17 @@ class _Screen:
asyncio.get_event_loop().stop() asyncio.get_event_loop().stop()
else: else:
self.content = Filler(Text(repr(term_code))) self.content = Filler(Text(repr(term_code)))
self._appearance_changed_event.set() APPEARANCE_CHANGED_EVENT.set()
def on_mouse_input(self, term_code): def on_mouse_input(self, term_code):
mouse_code = terminal.decode_mouse_input(term_code) mouse_code = terminal.decode_mouse_input(term_code)
self.content = Filler(Text(repr(term_code) + " " + repr(mouse_code))) self.content = Filler(Text(repr(term_code) + " " + repr(mouse_code)))
self._appearance_changed_event.set() APPEARANCE_CHANGED_EVENT.set()
def _main(): def _main():
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
appearance_changed_event = asyncio.Event() with context(loop, _Screen()):
screen = _Screen(appearance_changed_event)
with context(loop, appearance_changed_event, screen):
loop.run_forever() loop.run_forever()