Compare commits
2 commits
main
...
tab_suppor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f6e5fdea0 | ||
|
|
6ae41eaffa |
3 changed files with 271 additions and 204 deletions
6
build.py
6
build.py
|
|
@ -107,6 +107,12 @@ def create_font_specific_zip(font_path: Path, src_dir: Path, learn_projects_dir:
|
||||||
for builtin_app_dir in os.listdir("builtin_apps"):
|
for builtin_app_dir in os.listdir("builtin_apps"):
|
||||||
circup_cli(["--path", temp_dir, "install", "--auto", "--auto-file", f"apps/{builtin_app_dir}/code.py"],
|
circup_cli(["--path", temp_dir, "install", "--auto", "--auto-file", f"apps/{builtin_app_dir}/code.py"],
|
||||||
standalone_mode=False)
|
standalone_mode=False)
|
||||||
|
|
||||||
|
# install reqs for launcher and boot_animation
|
||||||
|
circup_cli(["--path", temp_dir, "install", "--auto", "--auto-file", f"code.py"], standalone_mode=False)
|
||||||
|
circup_cli(["--path", temp_dir, "install", "--auto", "--auto-file", f"boot_animation.py"], standalone_mode=False)
|
||||||
|
circup_cli(["--path", temp_dir, "install", "--auto", "--auto-file", f"boot.py"], standalone_mode=False)
|
||||||
|
|
||||||
os.remove(temp_dir / "boot_out.txt")
|
os.remove(temp_dir / "boot_out.txt")
|
||||||
# Create the final zip file
|
# Create the final zip file
|
||||||
with zipfile.ZipFile(output_zip, 'w', zipfile.ZIP_DEFLATED) as zf:
|
with zipfile.ZipFile(output_zip, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ from argv_file_helper import argv_filename
|
||||||
|
|
||||||
INPUT_DISPLAY_REFRESH_COOLDOWN = 0.3 # s
|
INPUT_DISPLAY_REFRESH_COOLDOWN = 0.3 # s
|
||||||
SHOW_MEMFREE = False
|
SHOW_MEMFREE = False
|
||||||
|
TAB_SIZE = 4 # Number of spaces to represent a tab
|
||||||
|
|
||||||
|
|
||||||
class MaybeDisableReload:
|
class MaybeDisableReload:
|
||||||
|
|
@ -79,34 +80,83 @@ class Buffer:
|
||||||
return len(self) - 1
|
return len(self) - 1
|
||||||
|
|
||||||
def insert(self, cursor, string):
|
def insert(self, cursor, string):
|
||||||
row, col = cursor.row, cursor.col
|
row, visual_col = cursor.row, cursor.col
|
||||||
# print(f"len: {len(self.lines)}")
|
actual_col = self._visual_to_actual_col(row, visual_col)
|
||||||
# print(f"row: {row}")
|
|
||||||
try:
|
try:
|
||||||
current = self.lines.pop(row)
|
current = self.lines.pop(row)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
current = ""
|
current = ""
|
||||||
new = current[:col] + string + current[col:]
|
|
||||||
|
new = current[:actual_col] + string + current[actual_col:]
|
||||||
self.lines.insert(row, new)
|
self.lines.insert(row, new)
|
||||||
|
|
||||||
def split(self, cursor):
|
def split(self, cursor):
|
||||||
row, col = cursor.row, cursor.col
|
row, visual_col = cursor.row, cursor.col
|
||||||
|
actual_col = self._visual_to_actual_col(row, visual_col)
|
||||||
|
|
||||||
current = self.lines.pop(row)
|
current = self.lines.pop(row)
|
||||||
self.lines.insert(row, current[:col])
|
self.lines.insert(row, current[:actual_col])
|
||||||
self.lines.insert(row + 1, current[col:])
|
self.lines.insert(row + 1, current[actual_col:])
|
||||||
|
|
||||||
def delete(self, cursor):
|
def delete(self, cursor):
|
||||||
row, col = cursor.row, cursor.col
|
row, visual_col = cursor.row, cursor.col
|
||||||
if (row, col) < (self.bottom, len(self[row])):
|
actual_col = self._visual_to_actual_col(row, visual_col)
|
||||||
|
|
||||||
|
if (row, actual_col) < (self.bottom, len(self[row])):
|
||||||
current = self.lines.pop(row)
|
current = self.lines.pop(row)
|
||||||
if col < len(current):
|
if actual_col < len(current):
|
||||||
new = current[:col] + current[col + 1:]
|
new = current[:actual_col] + current[actual_col + 1:]
|
||||||
self.lines.insert(row, new)
|
self.lines.insert(row, new)
|
||||||
else:
|
else:
|
||||||
nextline = self.lines.pop(row)
|
nextline = self.lines.pop(row)
|
||||||
new = current + nextline
|
new = current + nextline
|
||||||
self.lines.insert(row, new)
|
self.lines.insert(row, new)
|
||||||
|
|
||||||
|
def _visual_to_actual_col(self, row, visual_col):
|
||||||
|
"""Convert a visual column position to the actual buffer position"""
|
||||||
|
if row >= len(self.lines):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
line = self.lines[row]
|
||||||
|
actual_col = 0
|
||||||
|
current_visual_col = 0
|
||||||
|
|
||||||
|
while current_visual_col < visual_col and actual_col < len(line):
|
||||||
|
if line[actual_col] == '\t':
|
||||||
|
current_visual_col += TAB_SIZE
|
||||||
|
else:
|
||||||
|
current_visual_col += 1
|
||||||
|
|
||||||
|
if current_visual_col <= visual_col:
|
||||||
|
actual_col += 1
|
||||||
|
|
||||||
|
return actual_col
|
||||||
|
|
||||||
|
def actual_to_visual_col(self, row, actual_col):
|
||||||
|
"""Convert an actual buffer position to visual column position"""
|
||||||
|
if row >= len(self.lines) or actual_col > len(self.lines[row]):
|
||||||
|
return actual_col
|
||||||
|
|
||||||
|
line = self.lines[row]
|
||||||
|
visual_col = 0
|
||||||
|
|
||||||
|
for i in range(actual_col):
|
||||||
|
if i < len(line) and line[i] == '\t':
|
||||||
|
visual_col += TAB_SIZE
|
||||||
|
else:
|
||||||
|
visual_col += 1
|
||||||
|
|
||||||
|
return visual_col
|
||||||
|
|
||||||
|
def get_visual_length(self, row):
|
||||||
|
"""Get the visual length of a line, accounting for tabs"""
|
||||||
|
if row >= len(self.lines):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
line = self.lines[row]
|
||||||
|
return sum(TAB_SIZE if c == '\t' else 1 for c in line)
|
||||||
|
|
||||||
|
|
||||||
def clamp(x, lower, upper):
|
def clamp(x, lower, upper):
|
||||||
if x < lower:
|
if x < lower:
|
||||||
|
|
@ -142,42 +192,48 @@ class Cursor:
|
||||||
self._col_hint = col
|
self._col_hint = col
|
||||||
|
|
||||||
def _clamp_col(self, buffer):
|
def _clamp_col(self, buffer):
|
||||||
self._col = min(self._col_hint, len(buffer[self.row]))
|
visual_length = buffer.get_visual_length(self.row)
|
||||||
|
self._col = min(self._col_hint, visual_length)
|
||||||
|
|
||||||
def up(self, buffer): # pylint: disable=invalid-name
|
def up(self, buffer):
|
||||||
if self.row > 0:
|
if self.row > 0:
|
||||||
self.row -= 1
|
self.row -= 1
|
||||||
self._clamp_col(buffer)
|
self._clamp_col(buffer)
|
||||||
# print(f"cursor pos: {self.row}, {self.col}")
|
|
||||||
|
|
||||||
def down(self, buffer):
|
def down(self, buffer):
|
||||||
if self.row < len(buffer) - 1:
|
if self.row < len(buffer) - 1:
|
||||||
self.row += 1
|
self.row += 1
|
||||||
self._clamp_col(buffer)
|
self._clamp_col(buffer)
|
||||||
# print(f"cursor pos: {self.row}, {self.col}")
|
|
||||||
|
|
||||||
def left(self, buffer):
|
def left(self, buffer):
|
||||||
if self.col > 0:
|
if self.col > 0:
|
||||||
self.col -= 1
|
# Check if cursor is at the end of a tab
|
||||||
# print(f"cursor pos: {self.row}, {self.col}")
|
actual_col = buffer._visual_to_actual_col(self.row, self.col)
|
||||||
|
if actual_col > 0 and buffer[self.row][actual_col - 1] == '\t':
|
||||||
|
# If we're at a tab character, move visual position back by TAB_SIZE
|
||||||
|
self.col = buffer.actual_to_visual_col(self.row, actual_col - 1)
|
||||||
|
else:
|
||||||
|
self.col -= 1
|
||||||
elif self.row > 0:
|
elif self.row > 0:
|
||||||
self.row -= 1
|
self.row -= 1
|
||||||
self.col = len(buffer[self.row])
|
self.col = buffer.get_visual_length(self.row)
|
||||||
# print(f"cursor pos: {self.row}, {self.col}")
|
|
||||||
|
|
||||||
def right(self, buffer):
|
def right(self, buffer):
|
||||||
# print(f"len: {len(buffer)}")
|
visual_length = buffer.get_visual_length(self.row)
|
||||||
if len(buffer) > 0 and self.col < len(buffer[self.row]):
|
if self.col < visual_length:
|
||||||
self.col += 1
|
# Check if we're on a tab character
|
||||||
# print(f"cursor pos: {self.row}, {self.col}")
|
actual_col = buffer._visual_to_actual_col(self.row, self.col)
|
||||||
|
if actual_col < len(buffer[self.row]) and buffer[self.row][actual_col] == '\t':
|
||||||
|
# If we're on a tab character, move visual position forward by TAB_SIZE
|
||||||
|
self.col = buffer.actual_to_visual_col(self.row, actual_col + 1)
|
||||||
|
else:
|
||||||
|
self.col += 1
|
||||||
elif self.row < len(buffer) - 1:
|
elif self.row < len(buffer) - 1:
|
||||||
self.row += 1
|
self.row += 1
|
||||||
self.col = 0
|
self.col = 0
|
||||||
# print(f"cursor pos: {self.row}, {self.col}")
|
|
||||||
|
|
||||||
def end(self, buffer):
|
def end(self, buffer):
|
||||||
self.col = len(buffer[self.row])
|
self.col = buffer.get_visual_length(self.row)
|
||||||
# print(f"cursor pos: {self.row}, {self.col}")
|
|
||||||
|
|
||||||
|
|
||||||
class Window:
|
class Window:
|
||||||
|
|
@ -191,7 +247,7 @@ class Window:
|
||||||
def bottom(self):
|
def bottom(self):
|
||||||
return self.row + self.n_rows - 1
|
return self.row + self.n_rows - 1
|
||||||
|
|
||||||
def up(self, cursor): # pylint: disable=invalid-name
|
def up(self, cursor):
|
||||||
if cursor.row == self.row - 1 and self.row > 0:
|
if cursor.row == self.row - 1 and self.row > 0:
|
||||||
self.row -= 1
|
self.row -= 1
|
||||||
|
|
||||||
|
|
@ -219,7 +275,7 @@ def right(window, buffer, cursor):
|
||||||
window.horizontal_scroll(cursor)
|
window.horizontal_scroll(cursor)
|
||||||
|
|
||||||
|
|
||||||
def home(window, buffer, cursor): # pylint: disable=unused-argument
|
def home(window, buffer, cursor):
|
||||||
cursor.col = 0
|
cursor.col = 0
|
||||||
window.horizontal_scroll(cursor)
|
window.horizontal_scroll(cursor)
|
||||||
|
|
||||||
|
|
@ -229,22 +285,60 @@ def end(window, buffer, cursor):
|
||||||
window.horizontal_scroll(cursor)
|
window.horizontal_scroll(cursor)
|
||||||
|
|
||||||
|
|
||||||
def editor(stdscr, filename, mouse=None, terminal_tilegrid=None): # pylint: disable=too-many-branches,too-many-statements
|
def is_at_tab(buffer, row, col):
|
||||||
|
"""Check if a visual position corresponds to a tab character"""
|
||||||
|
if row >= len(buffer.lines):
|
||||||
|
return False
|
||||||
|
|
||||||
def _only_spaces_before(cursor):
|
actual_col = buffer._visual_to_actual_col(row, col)
|
||||||
i = cursor.col - 1
|
if actual_col >= len(buffer.lines[row]):
|
||||||
while i >= 0:
|
return False
|
||||||
print(f"i: {i} chr: '{buffer.lines[cursor.row][i]}'")
|
|
||||||
if buffer.lines[cursor.row][i] != " ":
|
return buffer.lines[row][actual_col] == '\t'
|
||||||
return False
|
|
||||||
i -= 1
|
|
||||||
return True
|
def get_tab_visual_positions(buffer, row, col):
|
||||||
|
"""Get all visual positions that belong to a tab at the current position"""
|
||||||
|
if not is_at_tab(buffer, row, col):
|
||||||
|
return []
|
||||||
|
|
||||||
|
actual_col = buffer._visual_to_actual_col(row, col)
|
||||||
|
visual_start = buffer.actual_to_visual_col(row, actual_col)
|
||||||
|
|
||||||
|
return list(range(visual_start, visual_start + TAB_SIZE))
|
||||||
|
|
||||||
|
|
||||||
|
def is_within_tab_range(buffer, row, col):
|
||||||
|
"""Check if a visual position is within any tab's visual range"""
|
||||||
|
if row >= len(buffer.lines):
|
||||||
|
return False
|
||||||
|
|
||||||
|
line = buffer.lines[row]
|
||||||
|
for i, char in enumerate(line):
|
||||||
|
if char == '\t':
|
||||||
|
visual_start = buffer.actual_to_visual_col(row, i)
|
||||||
|
visual_end = visual_start + TAB_SIZE
|
||||||
|
if visual_start <= col < visual_end:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def only_spaces_before(buffer, cursor):
|
||||||
|
"""Check if there are only spaces before the cursor"""
|
||||||
|
actual_col = buffer._visual_to_actual_col(cursor.row, cursor.col)
|
||||||
|
for i in range(actual_col):
|
||||||
|
if buffer.lines[cursor.row][i] != " ":
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def editor(stdscr, filename, mouse=None, terminal_tilegrid=None):
|
||||||
if os_exists(filename):
|
if os_exists(filename):
|
||||||
with open(filename, "r", encoding="utf-8") as f:
|
with open(filename, "r", encoding="utf-8") as f:
|
||||||
buffer = Buffer(f.read().splitlines())
|
buffer = Buffer(f.read().splitlines())
|
||||||
else:
|
else:
|
||||||
buffer = Buffer([""])
|
buffer = Buffer([""])
|
||||||
print(f"cwd: {os.getcwd()} | {os.getcwd() == "/apps/editor"}")
|
|
||||||
if os.getcwd() != "/apps/editor" and os.getcwd() != "/":
|
if os.getcwd() != "/apps/editor" and os.getcwd() != "/":
|
||||||
absolute_filepath = os.getcwd() + "/" + filename
|
absolute_filepath = os.getcwd() + "/" + filename
|
||||||
else:
|
else:
|
||||||
|
|
@ -257,12 +351,10 @@ def editor(stdscr, filename, mouse=None, terminal_tilegrid=None): # pylint: dis
|
||||||
|
|
||||||
window = Window(curses.LINES - 1, curses.COLS - 1)
|
window = Window(curses.LINES - 1, curses.COLS - 1)
|
||||||
cursor = Cursor()
|
cursor = Cursor()
|
||||||
terminal_tilegrid.pixel_shader[cursor.col,cursor.row] = [1, 0]
|
|
||||||
|
# Initialize cursor highlighting
|
||||||
|
highlight_cursor_position(terminal_tilegrid, buffer, cursor)
|
||||||
old_cursor_pos = (cursor.col, cursor.row)
|
old_cursor_pos = (cursor.col, cursor.row)
|
||||||
# try:
|
|
||||||
# visible_cursor.text = buffer[0][0]
|
|
||||||
# except IndexError:
|
|
||||||
# visible_cursor.text = " "
|
|
||||||
|
|
||||||
stdscr.erase()
|
stdscr.erase()
|
||||||
|
|
||||||
|
|
@ -272,19 +364,39 @@ def editor(stdscr, filename, mouse=None, terminal_tilegrid=None): # pylint: dis
|
||||||
if img[row] == line:
|
if img[row] == line:
|
||||||
return
|
return
|
||||||
img[row] = line
|
img[row] = line
|
||||||
line += " " * (window.n_cols - len(line))
|
# Calculate displayed length for padding
|
||||||
|
visual_length = sum(TAB_SIZE if c == '\t' else 1 for c in line)
|
||||||
|
padding = window.n_cols - visual_length
|
||||||
|
if padding > 0:
|
||||||
|
line += " " * padding
|
||||||
stdscr.addstr(row, 0, line)
|
stdscr.addstr(row, 0, line)
|
||||||
|
|
||||||
print(f"cwd: {os.getcwd()} | abs path: {absolute_filepath} | filename: {filename}")
|
|
||||||
while True:
|
while True:
|
||||||
lastrow = 0
|
lastrow = 0
|
||||||
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]):
|
||||||
lastrow = row
|
lastrow = row
|
||||||
|
|
||||||
if row == cursor.row - window.row and window.col > 0:
|
if row == cursor.row - window.row and window.col > 0:
|
||||||
line = "«" + line[window.col + 1:]
|
line = "«" + line[window.col + 1:]
|
||||||
if len(line) > window.n_cols:
|
|
||||||
line = line[: window.n_cols - 1] + "»"
|
# Calculate visual length for line truncation
|
||||||
|
visual_length = sum(TAB_SIZE if c == '\t' else 1 for c in line)
|
||||||
|
if visual_length > window.n_cols:
|
||||||
|
# Find the position to truncate based on visual length
|
||||||
|
actual_col = 0
|
||||||
|
current_visual_col = 0
|
||||||
|
while current_visual_col < window.n_cols - 1 and actual_col < len(line):
|
||||||
|
if line[actual_col] == '\t':
|
||||||
|
current_visual_col += TAB_SIZE
|
||||||
|
else:
|
||||||
|
current_visual_col += 1
|
||||||
|
actual_col += 1
|
||||||
|
|
||||||
|
if actual_col < len(line):
|
||||||
|
line = line[:actual_col] + "»"
|
||||||
|
|
||||||
setline(row, line)
|
setline(row, line)
|
||||||
|
|
||||||
for row in range(lastrow + 1, window.n_rows):
|
for row in range(lastrow + 1, window.n_rows):
|
||||||
setline(row, "~~ EOF ~~")
|
setline(row, "~~ EOF ~~")
|
||||||
row = curses.LINES - 1
|
row = curses.LINES - 1
|
||||||
|
|
@ -305,14 +417,14 @@ def editor(stdscr, filename, mouse=None, terminal_tilegrid=None): # pylint: dis
|
||||||
|
|
||||||
stdscr.move(*window.translate(cursor))
|
stdscr.move(*window.translate(cursor))
|
||||||
|
|
||||||
# display.refresh(minimum_frames_per_second=20)
|
|
||||||
k = stdscr.getkey()
|
k = stdscr.getkey()
|
||||||
if k is not None:
|
if k is not None:
|
||||||
# print(repr(k))
|
|
||||||
if len(k) == 1 and " " <= k <= "~":
|
if len(k) == 1 and " " <= k <= "~":
|
||||||
buffer.insert(cursor, k)
|
buffer.insert(cursor, k)
|
||||||
for _ in k:
|
right(window, buffer, cursor)
|
||||||
right(window, buffer, cursor)
|
elif k == "\t":
|
||||||
|
buffer.insert(cursor, k)
|
||||||
|
right(window, buffer, cursor)
|
||||||
elif k == "\x18": # ctrl-x
|
elif k == "\x18": # ctrl-x
|
||||||
if not util.readonly():
|
if not util.readonly():
|
||||||
with open(filename, "w", encoding="utf-8") as f:
|
with open(filename, "w", encoding="utf-8") as f:
|
||||||
|
|
@ -326,11 +438,6 @@ def editor(stdscr, filename, mouse=None, terminal_tilegrid=None): # pylint: dis
|
||||||
print(row)
|
print(row)
|
||||||
print("---- end file contents ----")
|
print("---- end file contents ----")
|
||||||
elif k == "\x13": # Ctrl-S
|
elif k == "\x13": # Ctrl-S
|
||||||
print(absolute_filepath)
|
|
||||||
print(f"starts with saves: {absolute_filepath.startswith("/saves/")}")
|
|
||||||
print(f"stars saves: {absolute_filepath.startswith("/saves/")}")
|
|
||||||
print(f"stars sd: {absolute_filepath.startswith("/sd/")}")
|
|
||||||
print(f"readonly: {util.readonly()}")
|
|
||||||
if (absolute_filepath.startswith("/saves/") or
|
if (absolute_filepath.startswith("/saves/") or
|
||||||
absolute_filepath.startswith("/sd/") or
|
absolute_filepath.startswith("/sd/") or
|
||||||
not util.readonly()):
|
not util.readonly()):
|
||||||
|
|
@ -363,24 +470,17 @@ def editor(stdscr, filename, mouse=None, terminal_tilegrid=None): # pylint: dis
|
||||||
working_directory=Path(filename).parent.absolute())
|
working_directory=Path(filename).parent.absolute())
|
||||||
supervisor.reload()
|
supervisor.reload()
|
||||||
elif k == "\x0f": # Ctrl-O
|
elif k == "\x0f": # Ctrl-O
|
||||||
|
|
||||||
supervisor.set_next_code_file("/apps/editor/code.py", sticky_on_reload=False, reload_on_error=True,
|
supervisor.set_next_code_file("/apps/editor/code.py", sticky_on_reload=False, reload_on_error=True,
|
||||||
working_directory="/apps/editor")
|
working_directory="/apps/editor")
|
||||||
supervisor.reload()
|
supervisor.reload()
|
||||||
|
|
||||||
|
|
||||||
elif k == "KEY_HOME":
|
elif k == "KEY_HOME":
|
||||||
home(window, buffer, cursor)
|
home(window, buffer, cursor)
|
||||||
elif k == "KEY_END":
|
elif k == "KEY_END":
|
||||||
end(window, buffer, cursor)
|
end(window, buffer, cursor)
|
||||||
elif k == "KEY_LEFT":
|
|
||||||
left(window, buffer, cursor)
|
|
||||||
elif k == "KEY_DOWN":
|
elif k == "KEY_DOWN":
|
||||||
|
|
||||||
cursor.down(buffer)
|
cursor.down(buffer)
|
||||||
window.down(buffer, cursor)
|
window.down(buffer, cursor)
|
||||||
window.horizontal_scroll(cursor)
|
window.horizontal_scroll(cursor)
|
||||||
print(f"scroll pos: {window.row}")
|
|
||||||
elif k == "KEY_PGDN":
|
elif k == "KEY_PGDN":
|
||||||
for _ in range(window.n_rows):
|
for _ in range(window.n_rows):
|
||||||
cursor.down(buffer)
|
cursor.down(buffer)
|
||||||
|
|
@ -397,39 +497,56 @@ def editor(stdscr, filename, mouse=None, terminal_tilegrid=None): # pylint: dis
|
||||||
window.horizontal_scroll(cursor)
|
window.horizontal_scroll(cursor)
|
||||||
elif k == "KEY_RIGHT":
|
elif k == "KEY_RIGHT":
|
||||||
right(window, buffer, cursor)
|
right(window, buffer, cursor)
|
||||||
|
elif k == "KEY_LEFT":
|
||||||
|
left(window, buffer, cursor)
|
||||||
elif k == "\n":
|
elif k == "\n":
|
||||||
leading_spaces = _count_leading_characters(buffer.lines[cursor.row], " ")
|
# Get leading whitespace from the current line
|
||||||
|
current_line = buffer.lines[cursor.row]
|
||||||
|
leading_spaces = _count_leading_characters(current_line, " ")
|
||||||
|
leading_tabs = _count_leading_characters(current_line, "\t")
|
||||||
|
|
||||||
|
# Split the line at the cursor
|
||||||
buffer.split(cursor)
|
buffer.split(cursor)
|
||||||
right(window, buffer, cursor)
|
|
||||||
|
# Move to the beginning of the next line
|
||||||
|
cursor.row += 1
|
||||||
|
cursor.col = 0
|
||||||
|
|
||||||
|
# Insert indentation in the correct order (first tabs, then spaces)
|
||||||
|
for i in range(leading_tabs):
|
||||||
|
buffer.insert(cursor, "\t")
|
||||||
|
right(window, buffer, cursor)
|
||||||
|
|
||||||
for i in range(leading_spaces):
|
for i in range(leading_spaces):
|
||||||
buffer.insert(cursor, " ")
|
buffer.insert(cursor, " ")
|
||||||
right(window, buffer, cursor)
|
right(window, buffer, cursor)
|
||||||
elif k in ("KEY_DELETE", "\x04"):
|
|
||||||
print("delete")
|
|
||||||
if cursor.row < len(buffer.lines) - 1 or \
|
|
||||||
cursor.col < len(buffer.lines[cursor.row]):
|
|
||||||
buffer.delete(cursor)
|
|
||||||
# try:
|
|
||||||
# visible_cursor.text = buffer.lines[cursor.row][cursor.col]
|
|
||||||
# except IndexError:
|
|
||||||
# visible_cursor.text = " "
|
|
||||||
|
|
||||||
|
# Update window scrolling
|
||||||
|
window.down(buffer, cursor)
|
||||||
|
window.horizontal_scroll(cursor)
|
||||||
|
|
||||||
|
elif k in ("KEY_DELETE", "\x04"):
|
||||||
|
if cursor.row < len(buffer.lines) - 1 or \
|
||||||
|
cursor.col < buffer.get_visual_length(cursor.row):
|
||||||
|
buffer.delete(cursor)
|
||||||
elif k in ("KEY_BACKSPACE", "\x7f", "\x08"):
|
elif k in ("KEY_BACKSPACE", "\x7f", "\x08"):
|
||||||
print(f"backspace {bytes(k, 'utf-8')}")
|
|
||||||
if (cursor.row, cursor.col) > (0, 0):
|
if (cursor.row, cursor.col) > (0, 0):
|
||||||
if cursor.col > 0 and buffer.lines[cursor.row][cursor.col-1] == " " and _only_spaces_before(cursor):
|
if cursor.col > 0 and only_spaces_before(buffer, cursor):
|
||||||
for i in range(4):
|
# Handle backspace with spaces
|
||||||
|
for i in range(min(TAB_SIZE, cursor.col)):
|
||||||
left(window, buffer, cursor)
|
left(window, buffer, cursor)
|
||||||
buffer.delete(cursor)
|
buffer.delete(cursor)
|
||||||
else:
|
else:
|
||||||
|
# Check if cursor is within a tab's visual space
|
||||||
|
if is_within_tab_range(buffer, cursor.row, cursor.col - 1):
|
||||||
|
actual_col = buffer._visual_to_actual_col(cursor.row, cursor.col - 1)
|
||||||
|
# Set cursor to the beginning of the tab
|
||||||
|
cursor.col = buffer.actual_to_visual_col(cursor.row, actual_col)
|
||||||
|
|
||||||
left(window, buffer, cursor)
|
left(window, buffer, cursor)
|
||||||
buffer.delete(cursor)
|
buffer.delete(cursor)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(f"unhandled k: {k}")
|
print(f"unhandled k: {k}")
|
||||||
print(f"unhandled K: {ord(k)}")
|
|
||||||
print(f"unhandled k: {bytes(k, 'utf-8')}")
|
|
||||||
|
|
||||||
|
|
||||||
if mouse is not None:
|
if mouse is not None:
|
||||||
pressed_btns = mouse.update()
|
pressed_btns = mouse.update()
|
||||||
|
|
@ -437,36 +554,58 @@ def editor(stdscr, filename, mouse=None, terminal_tilegrid=None): # pylint: dis
|
||||||
clicked_tile_coords[0] = mouse.x // 6
|
clicked_tile_coords[0] = mouse.x // 6
|
||||||
clicked_tile_coords[1] = mouse.y // 12
|
clicked_tile_coords[1] = mouse.y // 12
|
||||||
|
|
||||||
if clicked_tile_coords[0] > len(buffer.lines[clicked_tile_coords[1]]):
|
if clicked_tile_coords[1] < len(buffer.lines):
|
||||||
clicked_tile_coords[0] = len(buffer.lines[clicked_tile_coords[1]])
|
visual_length = buffer.get_visual_length(clicked_tile_coords[1])
|
||||||
|
if clicked_tile_coords[0] > visual_length:
|
||||||
|
clicked_tile_coords[0] = visual_length
|
||||||
|
|
||||||
cursor.row = clicked_tile_coords[1]
|
cursor.row = clicked_tile_coords[1]
|
||||||
cursor.col = clicked_tile_coords[0]
|
cursor.col = clicked_tile_coords[0]
|
||||||
|
|
||||||
|
# Update cursor highlighting if position changed
|
||||||
# print("updating visible cursor")
|
|
||||||
# print(f"anchored pos: {((cursor.col * 6) - 1, (cursor.row * 12) + 20)}")
|
|
||||||
|
|
||||||
if old_cursor_pos != (cursor.col, cursor.row):
|
if old_cursor_pos != (cursor.col, cursor.row):
|
||||||
# print(f"old cursor: {old_cursor_pos}, new: {(cursor.col, cursor.row)}")
|
# Clear old cursor position
|
||||||
terminal_tilegrid.pixel_shader[old_cursor_pos[0], old_cursor_pos[1]] = [0,1]
|
clear_cursor_highlight(terminal_tilegrid, buffer, old_cursor_pos[1], old_cursor_pos[0])
|
||||||
terminal_tilegrid.pixel_shader[cursor.col, cursor.row] = [1,0]
|
|
||||||
# print(f"old: {terminal_tilegrid.pixel_shader[old_cursor_pos[0], old_cursor_pos[1]]} new: {terminal_tilegrid.pixel_shader[cursor.col, cursor.row]}")
|
|
||||||
|
|
||||||
# visible_cursor.anchored_position = ((cursor.col * 6) - 1, (cursor.row * 12) + 20)
|
# Highlight new cursor position
|
||||||
|
highlight_cursor_position(terminal_tilegrid, buffer, cursor)
|
||||||
|
|
||||||
# visible_cursor.anchored_position = ((cursor.col * 6), ((cursor.row - window.row) * 12))
|
old_cursor_pos = (cursor.col, cursor.row)
|
||||||
#
|
|
||||||
# try:
|
|
||||||
# visible_cursor.text = buffer.lines[cursor.row][cursor.col]
|
|
||||||
# except IndexError:
|
|
||||||
# visible_cursor.text = " "
|
|
||||||
|
|
||||||
|
|
||||||
old_cursor_pos = (cursor.col, cursor.row)
|
def highlight_cursor_position(terminal_tilegrid, buffer, cursor):
|
||||||
|
"""Highlight the cursor position, handling tabs properly"""
|
||||||
|
if terminal_tilegrid is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if is_at_tab(buffer, cursor.row, cursor.col):
|
||||||
|
# If at a tab, highlight all positions that represent the tab
|
||||||
|
tab_positions = get_tab_visual_positions(buffer, cursor.row, cursor.col)
|
||||||
|
for pos in tab_positions:
|
||||||
|
terminal_tilegrid.pixel_shader[pos, cursor.row] = [1, 0]
|
||||||
|
else:
|
||||||
|
# Otherwise just highlight the current position
|
||||||
|
terminal_tilegrid.pixel_shader[cursor.col, cursor.row] = [1, 0]
|
||||||
|
|
||||||
|
|
||||||
|
def clear_cursor_highlight(terminal_tilegrid, buffer, row, col):
|
||||||
|
"""Clear cursor highlighting, handling tabs properly"""
|
||||||
|
if terminal_tilegrid is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if is_at_tab(buffer, row, col):
|
||||||
|
# If at a tab, clear all positions that represent the tab
|
||||||
|
tab_positions = get_tab_visual_positions(buffer, row, col)
|
||||||
|
for pos in tab_positions:
|
||||||
|
terminal_tilegrid.pixel_shader[pos, row] = [0, 1]
|
||||||
|
else:
|
||||||
|
# Otherwise just clear the current position
|
||||||
|
terminal_tilegrid.pixel_shader[col, row] = [0, 1]
|
||||||
|
|
||||||
|
|
||||||
def edit(filename, terminal=None, mouse=None, terminal_tilegrid=None):
|
def edit(filename, terminal=None, mouse=None, terminal_tilegrid=None):
|
||||||
with MaybeDisableReload():
|
with MaybeDisableReload():
|
||||||
if terminal is None:
|
if terminal is None:
|
||||||
return curses.wrapper(editor, filename)
|
return curses.wrapper(editor, filename)
|
||||||
else:
|
else:
|
||||||
return curses.custom_terminal_wrapper(terminal, editor, filename, mouse, terminal_tilegrid)
|
return curses.custom_terminal_wrapper(terminal, editor, filename, mouse, terminal_tilegrid)
|
||||||
130
src/code.py
130
src/code.py
|
|
@ -16,7 +16,7 @@ import supervisor
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import terminalio
|
import terminalio
|
||||||
import usb
|
|
||||||
import adafruit_pathlib as pathlib
|
import adafruit_pathlib as pathlib
|
||||||
from adafruit_bitmap_font import bitmap_font
|
from adafruit_bitmap_font import bitmap_font
|
||||||
from adafruit_display_text.text_box import TextBox
|
from adafruit_display_text.text_box import TextBox
|
||||||
|
|
@ -25,7 +25,8 @@ from adafruit_display_text.bitmap_label import Label
|
||||||
from adafruit_displayio_layout.layouts.grid_layout import GridLayout
|
from adafruit_displayio_layout.layouts.grid_layout import GridLayout
|
||||||
from adafruit_anchored_tilegrid import AnchoredTileGrid
|
from adafruit_anchored_tilegrid import AnchoredTileGrid
|
||||||
import adafruit_imageload
|
import adafruit_imageload
|
||||||
import adafruit_usb_host_descriptors
|
|
||||||
|
from adafruit_usb_host_mouse import find_and_init_boot_mouse
|
||||||
from adafruit_anchored_group import AnchoredGroup
|
from adafruit_anchored_group import AnchoredGroup
|
||||||
from adafruit_fruitjam.peripherals import request_display_config
|
from adafruit_fruitjam.peripherals import request_display_config
|
||||||
from adafruit_argv_file import read_argv, write_argv
|
from adafruit_argv_file import read_argv, write_argv
|
||||||
|
|
@ -78,86 +79,8 @@ bg_palette[0] = 0x222222
|
||||||
bg_tg = displayio.TileGrid(bitmap=background_bmp, pixel_shader=bg_palette)
|
bg_tg = displayio.TileGrid(bitmap=background_bmp, pixel_shader=bg_palette)
|
||||||
scaled_group.append(bg_tg)
|
scaled_group.append(bg_tg)
|
||||||
|
|
||||||
# load the mouse cursor bitmap
|
mouse = find_and_init_boot_mouse()
|
||||||
mouse_bmp = displayio.OnDiskBitmap("launcher_assets/mouse_cursor.bmp")
|
|
||||||
|
|
||||||
# make the background pink pixels transparent
|
|
||||||
mouse_bmp.pixel_shader.make_transparent(0)
|
|
||||||
|
|
||||||
# create a TileGrid for the mouse, using its bitmap and pixel_shader
|
|
||||||
mouse_tg = displayio.TileGrid(mouse_bmp, pixel_shader=mouse_bmp.pixel_shader)
|
|
||||||
|
|
||||||
# move it to the center of the display
|
|
||||||
mouse_tg.x = display.width // (2 * scale)
|
|
||||||
mouse_tg.y = display.height // (2 * scale)
|
|
||||||
# 046d:c52f
|
|
||||||
|
|
||||||
|
|
||||||
# mouse = usb.core.find(idVendor=0x046d, idProduct=0xc52f)
|
|
||||||
|
|
||||||
DIR_IN = 0x80
|
|
||||||
mouse_interface_index, mouse_endpoint_address = None, None
|
|
||||||
mouse = None
|
|
||||||
# scan for connected USB device and loop over any found
|
|
||||||
print("scanning usb")
|
|
||||||
for device in usb.core.find(find_all=True):
|
|
||||||
# print device info
|
|
||||||
print(f"{device.idVendor:04x}:{device.idProduct:04x}")
|
|
||||||
print(device.manufacturer, device.product)
|
|
||||||
print()
|
|
||||||
config_descriptor = adafruit_usb_host_descriptors.get_configuration_descriptor(
|
|
||||||
device, 0
|
|
||||||
)
|
|
||||||
print(config_descriptor)
|
|
||||||
#
|
|
||||||
# i = 0
|
|
||||||
# while i < len(config_descriptor):
|
|
||||||
# descriptor_len = config_descriptor[i]
|
|
||||||
# descriptor_type = config_descriptor[i + 1]
|
|
||||||
# if descriptor_type == adafruit_usb_host_descriptors.DESC_CONFIGURATION:
|
|
||||||
# config_value = config_descriptor[i + 5]
|
|
||||||
# print(f" value {config_value:d}")
|
|
||||||
# elif descriptor_type == adafruit_usb_host_descriptors.DESC_INTERFACE:
|
|
||||||
# interface_number = config_descriptor[i + 2]
|
|
||||||
# interface_class = config_descriptor[i + 5]
|
|
||||||
# interface_subclass = config_descriptor[i + 6]
|
|
||||||
# interface_protocol = config_descriptor[i + 7]
|
|
||||||
# print(f" interface[{interface_number:d}]")
|
|
||||||
# print(
|
|
||||||
# f" class {interface_class:02x} subclass {interface_subclass:02x}"
|
|
||||||
# )
|
|
||||||
# print(f"protocol: {interface_protocol}")
|
|
||||||
# elif descriptor_type == adafruit_usb_host_descriptors.DESC_ENDPOINT:
|
|
||||||
# endpoint_address = config_descriptor[i + 2]
|
|
||||||
# if endpoint_address & DIR_IN:
|
|
||||||
# print(f" IN {endpoint_address:02x}")
|
|
||||||
# else:
|
|
||||||
# print(f" OUT {endpoint_address:02x}")
|
|
||||||
# i += descriptor_len
|
|
||||||
# print()
|
|
||||||
#
|
|
||||||
# # assume the device is the mouse
|
|
||||||
# mouse = device
|
|
||||||
_possible_interface_index, _possible_endpoint_address = adafruit_usb_host_descriptors.find_boot_mouse_endpoint(device)
|
|
||||||
if _possible_interface_index is not None and _possible_endpoint_address is not None:
|
|
||||||
mouse = device
|
|
||||||
mouse_interface_index = _possible_interface_index
|
|
||||||
mouse_endpoint_address = _possible_endpoint_address
|
|
||||||
print(f"mouse interface: {mouse_interface_index} endpoint_address: {hex(mouse_endpoint_address)}")
|
|
||||||
|
|
||||||
mouse_was_attached = None
|
|
||||||
if mouse is not None:
|
|
||||||
# detach the kernel driver if needed
|
|
||||||
if mouse.is_kernel_driver_active(0):
|
|
||||||
mouse_was_attached = True
|
|
||||||
mouse.detach_kernel_driver(0)
|
|
||||||
else:
|
|
||||||
mouse_was_attached = False
|
|
||||||
|
|
||||||
# set configuration on the mouse so we can use it
|
|
||||||
mouse.set_configuration()
|
|
||||||
|
|
||||||
mouse_buf = array.array("b", [0] * 8)
|
|
||||||
WIDTH = 280
|
WIDTH = 280
|
||||||
HEIGHT = 182
|
HEIGHT = 182
|
||||||
|
|
||||||
|
|
@ -370,8 +293,10 @@ if len(apps) <= 6:
|
||||||
right_tg.hidden = True
|
right_tg.hidden = True
|
||||||
left_tg.hidden = True
|
left_tg.hidden = True
|
||||||
|
|
||||||
if mouse:
|
if mouse is not None:
|
||||||
scaled_group.append(mouse_tg)
|
mouse.scale = 2
|
||||||
|
mouse.x = display.width - 6
|
||||||
|
scaled_group.append(mouse.tilegrid)
|
||||||
|
|
||||||
|
|
||||||
help_txt = Label(terminalio.FONT, text="[Arrow]: Move\n[E]: Edit\n[Enter]: Run")
|
help_txt = Label(terminalio.FONT, text="[Arrow]: Move\n[E]: Edit\n[Enter]: Run")
|
||||||
|
|
@ -391,8 +316,8 @@ def atexit_callback():
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
print("inside atexit callback")
|
print("inside atexit callback")
|
||||||
if mouse_was_attached and not mouse.is_kernel_driver_active(0):
|
if mouse is not None:
|
||||||
mouse.attach_kernel_driver(0)
|
mouse.release()
|
||||||
|
|
||||||
|
|
||||||
atexit.register(atexit_callback)
|
atexit.register(atexit_callback)
|
||||||
|
|
@ -501,7 +426,7 @@ def handle_key_press(key):
|
||||||
|
|
||||||
|
|
||||||
print(f"apps: {apps}")
|
print(f"apps: {apps}")
|
||||||
print(mouse_interface_index, mouse_endpoint_address)
|
# print(mouse_interface_index, mouse_endpoint_address)
|
||||||
while True:
|
while True:
|
||||||
index = None
|
index = None
|
||||||
editor_index = None
|
editor_index = None
|
||||||
|
|
@ -517,26 +442,23 @@ while True:
|
||||||
# app_titles[selected].background_color = 0x008800
|
# app_titles[selected].background_color = 0x008800
|
||||||
|
|
||||||
if mouse:
|
if mouse:
|
||||||
try:
|
pressed_btns = mouse.update()
|
||||||
# attempt to read data from the mouse
|
|
||||||
# 10ms timeout, so we don't block long if there
|
|
||||||
# is no data
|
|
||||||
count = mouse.read(mouse_endpoint_address, mouse_buf, timeout=20)
|
|
||||||
except usb.core.USBTimeoutError:
|
|
||||||
# skip the rest of the loop if there is no data
|
|
||||||
count = 0
|
|
||||||
|
|
||||||
# update the mouse tilegrid x and y coordinates
|
if pressed_btns is not None and "left" in pressed_btns:
|
||||||
# based on the delta values read from the mouse
|
print("left click")
|
||||||
if count > 0:
|
clicked_cell = menu_grid.which_cell_contains((mouse.x, mouse.y))
|
||||||
mouse_tg.x = max(0, min((display.width // scale) - 1, mouse_tg.x + mouse_buf[1]))
|
if clicked_cell is not None:
|
||||||
mouse_tg.y = max(0, min((display.height // scale) - 1, mouse_tg.y + mouse_buf[2]))
|
index = clicked_cell[1] * config["width"] + clicked_cell[0]
|
||||||
|
|
||||||
if mouse_buf[0] & (1 << 0) != 0:
|
clicked_coords = (mouse.x, mouse.y, 0)
|
||||||
print("left click")
|
if left_tg.contains(clicked_coords):
|
||||||
clicked_cell = menu_grid.which_cell_contains((mouse_tg.x, mouse_tg.y))
|
if cur_page > 0:
|
||||||
if clicked_cell is not None:
|
cur_page -= 1
|
||||||
index = clicked_cell[1] * config["width"] + clicked_cell[0]
|
display_page(cur_page)
|
||||||
|
elif right_tg.contains(clicked_coords):
|
||||||
|
if cur_page < math.ceil(len(apps) / 6) - 1:
|
||||||
|
cur_page += 1
|
||||||
|
display_page(cur_page)
|
||||||
|
|
||||||
if index is not None:
|
if index is not None:
|
||||||
print("index", index)
|
print("index", index)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue