Last active
April 6, 2024 00:35
-
-
Save impiaaa/b24cc7ecc5096728c4fb27fce6d94ead to your computer and use it in GitHub Desktop.
Highlight file changes as they happen
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import curses, sys, difflib | |
import time, os.path | |
import watchdog.events, watchdog.observers | |
class Drawer: | |
def __init__(self, win): | |
self.xoffset = self.yoffset = 0 | |
self.win = win | |
def draw(self, event_handler): | |
self.win.erase() | |
height, width = self.win.getmaxyx() | |
first_column = 0 if curses.has_colors() else 1 | |
if not event_handler.first: | |
for tag, i1, i2, j1, j2 in event_handler.matcher.get_opcodes(): | |
if curses.has_colors(): | |
self.win.attrset(curses.color_pair({'replace': 1, | |
'insert': 2, | |
'delete': 3, | |
'equal': 0 | |
}[tag])) | |
for i in range(j1, j2): | |
if i-self.yoffset < 0: continue | |
if i-self.yoffset >= height: break | |
if not curses.has_colors(): | |
self.win.addnstr(i-self.yoffset, 0, {'replace': '*', | |
'insert': '+', | |
'delete': '-', | |
'equal': ' ' | |
}[tag], 1) | |
self.win.addnstr(i-self.yoffset, first_column, event_handler.contents[i][self.xoffset:], width-first_column) | |
else: | |
for i, line in enumerate(event_handler.contents): | |
if i-self.yoffset < 0: continue | |
if i-self.yoffset >= height: break | |
self.win.addnstr(i-self.yoffset, first_column, line[self.xoffset:], width-first_column) | |
self.win.refresh() | |
def input(self, linecount): | |
c = self.win.getch() | |
height, width = self.win.getmaxyx() | |
if 0 < c < 256 and chr(c) in 'Qq': | |
return True | |
elif c == curses.KEY_UP: | |
if self.yoffset > 0: | |
self.yoffset -= 1 | |
else: | |
curses.beep() | |
elif c == curses.KEY_DOWN: | |
if self.yoffset+height < linecount: | |
self.yoffset += 1 | |
else: | |
curses.beep() | |
elif c == curses.KEY_LEFT: | |
if self.xoffset > 0: | |
self.xoffset -= 1 | |
else: | |
curses.beep() | |
elif c == curses.KEY_RIGHT: | |
self.xoffset += 1 | |
class Handler(watchdog.events.PatternMatchingEventHandler): | |
def __init__(self, path): | |
super().__init__(patterns=[path]) | |
self.path = os.path.abspath(path) | |
self.matcher = difflib.SequenceMatcher(isjunk=lambda s: s.isspace()) | |
self.contents = None | |
self.first = True | |
self.read() | |
def read(self): | |
old = self.contents | |
with open(self.path) as f: | |
self.contents = f.read().splitlines() | |
if old is not None: | |
self.matcher.set_seqs(old, self.contents) | |
self.first = False | |
def on_deleted(self, event): | |
self.path = None | |
def on_modified(self, event): | |
if self.path is None: return | |
self.read() | |
self.drawer.draw(self) | |
def on_moved(self, event): | |
if self.path is None: return | |
self.path = event.dest_path | |
self.patterns[:] = [event.dest_path] | |
# TODO: restart the observer if it moved to a different directory | |
event_handler = Handler(sys.argv[1]) | |
observer = watchdog.observers.Observer() | |
observer.schedule(event_handler, os.path.dirname(event_handler.path)) | |
def mainloop(win): | |
curses.curs_set(0) | |
if curses.has_colors(): | |
curses.init_pair(1, 0, curses.COLOR_BLUE) | |
curses.init_pair(2, 0, curses.COLOR_GREEN) | |
curses.init_pair(3, 0, curses.COLOR_RED) | |
win.nodelay(False) | |
drawer = Drawer(win) | |
event_handler.drawer = drawer | |
observer.start() | |
while event_handler.path is not None: | |
drawer.draw(event_handler) | |
if drawer.input(len(event_handler.contents)): | |
break | |
observer.stop() | |
curses.curs_set(1) | |
curses.wrapper(mainloop) | |
observer.join() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment