Fruit-Jam-OS/builtin_apps/editor/adafruit_editor/picker.py

192 lines
5.6 KiB
Python

# SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import os
import time
import usb_cdc
from . import dang as curses
from . import util
# pylint: disable=redefined-builtin
# def print(message):
# usb_cdc.data.write(f"{message}\r\n".encode("utf-8"))
always = ["code.py", "boot.py", "settings.toml", "boot_out.txt"]
good_extensions = [".py", ".toml", ".txt", ".json"]
def os_exists(filename):
try:
os.stat(filename)
return True
except OSError:
return False
def isdir(filename):
return os.stat(filename)[0] & 0o40_000
def has_good_extension(filename):
for g in good_extensions:
if filename.endswith(g):
return True
return False
def picker(stdscr, options, notes=(), start_idx=0):
stdscr.erase()
visible_files = None
if len(options) > curses.LINES - 1:
visible_files = options[:curses.LINES - 1]
else:
visible_files = options
scroll_offset = 0
need_to_scroll = False
# del options[curses.LINES - 1:]
print(f"len opts: {len(options)}")
print(f"len vis: {len(visible_files)}")
def _draw_file_list():
for row, option in enumerate(visible_files):
if row < len(notes) and (note := notes[row]):
option = f"{option} {note}"
try:
space_count = max(len(visible_files[row + 1]), len(visible_files[row - 1])) - len(option)
if space_count < 0:
space_count = 0
except IndexError:
space_count = curses.COLS - len(option)
stdscr.addstr(row, 3, option + " " * space_count)
stdscr.addstr(curses.LINES - 1, 0, "Enter: select | ^C: quit | ^N: New")
_draw_file_list()
old_idx = None
idx = start_idx
while True:
if need_to_scroll:
need_to_scroll = False
_draw_file_list()
if idx != old_idx:
if old_idx is not None:
stdscr.addstr(old_idx, 0, " ")
stdscr.addstr(idx, 0, "=>")
old_idx = idx
k = stdscr.getkey()
if k == "KEY_DOWN":
print(f"{scroll_offset + len(visible_files)} < {len(options)}")
if scroll_offset + len(visible_files) < len(options):
if idx == len(visible_files) - 1:
need_to_scroll = True
scroll_offset += 1
visible_files = options[scroll_offset:scroll_offset + curses.LINES - 1]
idx = min(idx + 1, len(visible_files) - 1)
elif k == "KEY_UP":
if scroll_offset > 0:
if idx == 0:
need_to_scroll = True
scroll_offset -= 1
visible_files = options[scroll_offset:scroll_offset + curses.LINES - 1]
idx = max(idx - 1, 0)
elif k == "\n":
if visible_files[idx] == "../":
os.chdir("../")
options, notes = _files_list()
return picker(stdscr, options, notes)
elif isdir(visible_files[idx]):
os.chdir(visible_files[idx])
options, notes = _files_list()
return picker(stdscr, options, notes)
else:
return visible_files[idx]
# ctrl-N
elif k == "\x0E":
# if not util.readonly():
new_file_name = new_file(stdscr)
if new_file_name is not None:
return new_file_name
else:
time.sleep(2)
stdscr.erase()
old_idx = None
_draw_file_list()
def terminal_input(stdscr, message):
stdscr.erase()
stdscr.addstr(0, 0, message)
input_str_list = []
k = stdscr.getkey()
while k != "\n":
if len(k) == 1 and " " <= k <= "~":
input_str_list.append(k)
stdscr.addstr(0, len(message) + len(input_str_list) - 1, k)
elif k == "\x08":
input_str_list.pop(len(input_str_list) - 1)
stdscr.addstr(0, len(message) + len(input_str_list) - 1, k)
k = stdscr.getkey()
# submit after enter pressed
return "".join(input_str_list)
# pylint: disable=inconsistent-return-statements
def new_file(stdscr):
stdscr.erase()
new_file_name = terminal_input(stdscr, "New File Name: ")
if os_exists(new_file_name):
stdscr.addstr(1,0, "Error: File Already Exists")
return
print(f"new filename: {new_file_name}")
if not new_file_name.startswith("/saves/") and not new_file_name.startswith("/sd/"):
if not util.readonly():
with open(new_file_name, "w") as f:
f.write("")
return new_file_name
else:
stdscr.addstr(1, 0, "Error: Cannot create file in readonly storage")
else:
with open(new_file_name, "w") as f:
f.write("")
return new_file_name
def _files_list():
options = sorted(
(
g
for g in os.listdir(".")
if not g.startswith(".")
),
key=lambda filename: (not has_good_extension(filename), filename),
) # + always[:]
if os.getcwd() != "/":
options.insert(0, "../")
# notes = [None if os_exists(filename) else "(NEW)" for filename in options]
notes = [None] * len(options)
return options, notes
def pick_file(terminal):
os.chdir("/")
options, notes = _files_list()
return curses.custom_terminal_wrapper(terminal, picker, options, notes)