Adapt to run under circuitpython

This commit is contained in:
Jeff Epler 2023-07-05 13:54:26 -05:00
parent a24013a70c
commit 511cd81acb
No known key found for this signature in database
GPG key ID: D5BF15AB975AB4DE
4 changed files with 201 additions and 18 deletions

5
code.py Normal file
View file

@ -0,0 +1,5 @@
import editor
try:
editor.edit("code.py")
except KeyboardInterrupt:
pass

105
dang.py Normal file
View file

@ -0,0 +1,105 @@
import select
import sys
try:
import termios
_orig_attr = None
def _nonblocking():
global _orig_attr
_orig_attr = termios.tcgetattr(sys.stdin)
attr = termios.tcgetattr(sys.stdin)
attr[3] &= ~(termios.ECHO | termios.ICANON)
attr[6][termios.VMIN] = 1
attr[6][termios.VTIME] = 0
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, attr)
def _blocking():
if _orig_attr is not None:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, _orig_attr)
except:
def _nonblocking():
pass
def _blocking():
pass
LINES = 24
COLS = 80
special_keys = {
'\x1b': ..., # all prefixes of special keys must be entered as Ellipsis
'\x1b[': ...,
'\x1b[A': "KEY_UP",
'\x1b[B': "KEY_DOWN",
'\x1b[C': "KEY_RIGHT",
'\x1b[D': "KEY_LEFT",
'\x1b[H': "KEY_HOME",
'\x1b[F': "KEY_END",
}
class Screen:
def __init__(self):
self._poll = select.poll()
self._poll.register(sys.stdin, select.POLLIN)
self._pending = ''
def _sys_stdin_readable(self):
return hasattr(sys.stdin, 'readable') and sys.stdin.readable()
def _sys_stdout_flush(self):
if hasattr(sys.stdout, 'flush'):
sys.stdout.flush()
def _terminal_read_blocking(self):
return sys.stdin.read(1)
def _terminal_read_timeout(self, timeout):
if self._sys_stdin_readable() or self._poll.poll(timeout):
r = sys.stdin.read(1)
return r
return None
def move(self, y, x):
print(end=f"\033[{y+1};{x+1}H")
def erase(self):
print(end="\033H\033[2J")
def addstr(self, y, x, text):
self.move(y, x)
print(end=text)
def getkey(self):
self._sys_stdout_flush()
pending = self._pending
if pending and (code := special_keys.get(pending)) is None:
self._pending = pending[1:]
return pending[0]
while True:
if pending:
c = self._terminal_read_timeout(50)
if c is None:
self._pending = pending[1:]
return pending[0]
else:
c = self._terminal_read_blocking()
c = pending + c
code = special_keys.get(c)
if code is None:
self._pending = c[1:]
return c[0]
elif code is not Ellipsis:
return code
pending = c
def wrapper(func, *args, **kwds):
stdscr = Screen()
try:
_nonblocking()
func(stdscr, *args, **kwds)
finally:
_blocking()
stdscr.move(LINES-1, 0)
print("\n")

7
dangtest.py Normal file
View file

@ -0,0 +1,7 @@
from dang import wrapper
def main(stdscr):
while True:
print(repr(stdscr.getkey()))
wrapper(main)

102
editor.py
View file

@ -1,7 +1,38 @@
import argparse
import curses
import dang as curses
import sys
import gc
class MaybeDisableReload:
def __enter__(self):
try:
from supervisor import runtime
except ImportError:
return
self._old_autoreload = runtime.autoreload
runtime.autoreload = False
def __exit__(self, exc_type, exc_value, traceback):
try:
from supervisor import runtime
except ImportError:
return
runtime.autoreload = self._old_autoreload
def gc_mem_free_hint():
if hasattr(gc, 'mem_free'):
gc.collect()
return f" | free: {gc.mem_free()}"
return ""
def readonly():
try:
import storage
except ImportError:
return False
return storage.getmount('/').readonly
class Buffer:
def __init__(self, lines):
@ -92,6 +123,8 @@ class Cursor:
self.row += 1
self.col = 0
def end(self, buffer):
self.col = len(buffer[self.row])
class Window:
def __init__(self, n_rows, n_cols, row=0, col=0):
@ -125,37 +158,67 @@ def left(window, buffer, cursor):
window.up(cursor)
window.horizontal_scroll(cursor)
def right(window, buffer, cursor):
cursor.right(buffer)
window.down(buffer, cursor)
window.horizontal_scroll(cursor)
def home(window, buffer, cursor):
cursor.col = 0
window.horizontal_scroll(cursor)
def main(stdscr):
parser = argparse.ArgumentParser()
parser.add_argument("filename")
args = parser.parse_args()
def end(window, buffer, cursor):
cursor.end(buffer)
window.horizontal_scroll(cursor)
with open(args.filename) as f:
def editor(stdscr, filename):
with open(filename) as f:
buffer = Buffer(f.read().splitlines())
window = Window(curses.LINES - 1, curses.COLS - 1)
cursor = Cursor()
stdscr.erase()
img = [''] * window.n_rows
def setline(row, line):
if img[row] == line:
return
img[row] = line
stdscr.addstr(row, 0, line)
while True:
stdscr.erase()
for row, line in enumerate(buffer[window.row:window.row + window.n_rows]):
for row, line in enumerate(buffer[window.row:window.row + window.n_rows-1]):
if row == cursor.row - window.row and window.col > 0:
line = "«" + line[window.col + 1:]
if len(line) > window.n_cols:
line = line[:window.n_cols - 1] + "»"
stdscr.addstr(row, 0, line)
line += ' ' * (window.n_cols - len(line))
setline(row, line)
row = window.n_rows - 1
if readonly():
line = f"{filename:12} (readonly) | ^C: quit{gc_mem_free_hint()}"
else:
line = f"{filename:12} | ^X: write & exit | ^C: quit w/o save{gc_mem_free_hint()}"
setline(row, line)
stdscr.move(*window.translate(cursor))
k = stdscr.getkey()
if k == "q":
sys.exit(0)
if len(k) == 1 and ' ' <= k <= '~':
buffer.insert(cursor, k)
for _ in k:
right(window, buffer, cursor)
elif k == "\x18" and not readonly: # ctrl-x
with open(filename, "w") as f:
for row in buffer:
f.write(row)
return
elif k == "KEY_HOME":
home(window, buffer, cursor)
elif k == "KEY_END":
end(window, buffer, cursor)
elif k == "KEY_LEFT":
left(window, buffer, cursor)
elif k == "KEY_DOWN":
@ -177,11 +240,14 @@ def main(stdscr):
if (cursor.row, cursor.col) > (0, 0):
left(window, buffer, cursor)
buffer.delete(cursor)
else:
buffer.insert(cursor, k)
for _ in k:
right(window, buffer, cursor)
def edit(filename):
with MaybeDisableReload():
curses.wrapper(editor, filename)
if __name__ == "__main__":
curses.wrapper(main)
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("filename")
args = parser.parse_args()
edit(filename)