Files
t-deckard/app/ed.py
T

202 lines
5.7 KiB
Python
Raw Normal View History

2025-12-21 11:13:39 +02:00
# A simple line-oriented text editor for MicroPython/CircuitPython
# implementing the POSIX 'ed' subset
# Usage: from app.ed import edit
# Created by Luxferre in 2025, released into public domain
from deck.input import input
class Ed:
def __init__(self, filename=None):
self.filename = filename
self.buffer = []
self.current_line = 0 # 1-indexed for the user, 0 means empty or before first line
self.dirty = False
self.last_error = ""
if filename:
try:
with open(filename, "r") as f:
self.buffer = [line.rstrip("\n\r") for line in f.readlines()]
self.current_line = len(self.buffer)
print(len("".join(self.buffer)) + len(self.buffer)) # Print byte count
except OSError:
print(f"cannot open input file {filename}")
def _parse_address(self, addr_str):
"""Simple address parser for line numbers, '.', and '$'."""
if not addr_str:
return self.current_line
if addr_str == ".":
return self.current_line
if addr_str == "$":
return len(self.buffer)
try:
return int(addr_str)
except ValueError:
return None
def _get_range(self, addr_range):
"""Parses ranges like '1,5' or '%'."""
if not addr_range:
return self.current_line, self.current_line
if addr_range == ",":
return 1, len(self.buffer)
if addr_range == "%":
return 1, len(self.buffer)
parts = addr_range.split(",")
if len(parts) == 1:
addr = self._parse_address(parts[0])
return addr, addr
else:
return self._parse_address(parts[0]), self._parse_address(parts[1])
def run(self):
while True:
try:
2025-12-21 11:15:26 +02:00
cmd_line = input(":").strip()
2025-12-21 11:13:39 +02:00
except EOFError:
break
if not cmd_line:
# A null command (just newline) prints the current line
if self.buffer:
print(self.buffer[self.current_line - 1])
continue
# Basic command regex-like split
# Format: [address]command[parameters]
cmd_char = ""
addr_part = ""
for i, char in enumerate(cmd_line):
if char.isalpha():
addr_part = cmd_line[:i]
cmd_char = char
params = cmd_line[i+1:].strip()
break
else:
# If no alpha command found, try interpreting as address only
addr = self._parse_address(cmd_line)
if addr is not None and 0 <= addr <= len(self.buffer):
self.current_line = addr
if self.current_line > 0:
print(self.buffer[self.current_line - 1])
continue
else:
print("?")
continue
try:
start, end = self._get_range(addr_part)
if cmd_char == 'q':
if self.dirty and params != "!":
print("?")
self.dirty = False # Standard ed behavior: second q forces exit
continue
break
elif cmd_char == 'p' or cmd_char == 'n':
if not self.buffer:
print("?")
continue
start = max(1, start)
end = min(len(self.buffer), end)
for i in range(start - 1, end):
if cmd_char == 'n':
print(f"{i + 1}\t{self.buffer[i]}")
else:
print(self.buffer[i])
self.current_line = end
elif cmd_char == 'a':
idx = start
while True:
line = input("")
if line == ".":
break
self.buffer.insert(idx, line)
idx += 1
self.dirty = True
self.current_line = idx
elif cmd_char == 'i':
idx = max(0, start - 1)
while True:
line = input("")
if line == ".":
break
self.buffer.insert(idx, line)
idx += 1
self.dirty = True
self.current_line = idx
2025-12-21 11:24:51 +02:00
elif cmd_char == 'c':
if not self.buffer:
print("?")
continue
del self.buffer[start-1 : end]
idx = start - 1
while True:
line = input("")
if line == ".":
break
self.buffer.insert(idx, line)
idx += 1
self.dirty = True
self.current_line = idx
2025-12-21 11:13:39 +02:00
elif cmd_char == 'd':
if not self.buffer:
print("?")
continue
del self.buffer[start-1 : end]
self.current_line = min(start, len(self.buffer))
self.dirty = True
2025-12-21 11:24:51 +02:00
elif cmd_char == 's':
if not self.buffer or not params:
print("?")
continue
# Simple s/old/new/ parser
sep = params[0]
parts = params.split(sep)
if len(parts) < 3:
print("?")
continue
old, new = parts[1], parts[2]
start = max(1, start)
end = min(len(self.buffer), end)
for i in range(start - 1, end):
if old in self.buffer[i]:
self.buffer[i] = self.buffer[i].replace(old, new)
self.dirty = True
self.current_line = end
2025-12-21 11:13:39 +02:00
elif cmd_char == 'w':
target = params if params else self.filename
if not target:
print("?")
continue
with open(target, "w") as f:
for line in self.buffer:
f.write(line + "\n")
print(len("".join(self.buffer)) + len(self.buffer))
self.dirty = False
self.filename = target
elif cmd_char == 'f':
if params:
self.filename = params
print(self.filename if self.filename else "No current filename")
else:
print("?")
except Exception:
print("?")
def edit(fname):
editor = Ed(fname)
editor.run()