Adapt to run under circuitpython
This commit is contained in:
parent
a24013a70c
commit
511cd81acb
4 changed files with 201 additions and 18 deletions
5
code.py
Normal file
5
code.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import editor
|
||||
try:
|
||||
editor.edit("code.py")
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
105
dang.py
Normal file
105
dang.py
Normal 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
7
dangtest.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from dang import wrapper
|
||||
|
||||
def main(stdscr):
|
||||
while True:
|
||||
print(repr(stdscr.getkey()))
|
||||
|
||||
wrapper(main)
|
||||
102
editor.py
102
editor.py
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue