new structure

This commit is contained in:
Maddie H 2022-03-15 15:37:38 +00:00
parent ddf99d71f6
commit 88f7ab0e8d
17 changed files with 195 additions and 95 deletions

View File

@ -1,5 +0,0 @@
{
"python.analysis.extraPaths": [
"./modes"
]
}

Binary file not shown.

0
core/buffer.py Normal file
View File

View File

@ -1,5 +1,29 @@
def cursor_mode(mode): def cursor_mode(mode):
if mode == "block": if mode == "block":
print("\033[2 q") print("\033[2 q")
elif mode == "line": elif mode == "line":
print("\033[6 q") print("\033[6 q")
elif mode == "hidden":
print('\033[? 25l')
elif mode == "visible":
print('\033[? 25h')
def check_cursor(data):
data["cursor_x"] = max(2, data["cursor_x"])
data["cursor_x"] = min(data["width"] - 1, data["cursor_x"])
data["cursor_y"] = max(0, data["cursor_y"])
data["cursor_y"] = min(data["height"] - 3, data["cursor_y"])
return data
def move(stdscr, data):
# Calculate a valid cursor position from data
data = check_cursor(data)
# Move the cursor
stdscr.move(data["cursor_y"], data["cursor_x"])

17
core/mode.py Normal file
View File

@ -0,0 +1,17 @@
from modes import normal, insert, command
def activate(stdscr, data):
if data["mode"] == "normal":
data["mode_color"] = 6
data = normal.activate(stdscr, data)
elif data["mode"] == "insert":
data["mode_color"] = 12
data = insert.activate(stdscr, data)
elif data["mode"] == "command":
data["mode_color"] = 6
data = command.activate(stdscr, data)
return data

View File

@ -1,35 +1,43 @@
import curses import curses
def refresh(stdscr, height, width, mode, colors=None, theme="inverted", icon="λ", file="[No Name]"): def themes(data):
if colors is None: if data["statusbar_theme"] == "bare":
colors = [8, 6, 14, 2] # The theme colors
colors = (7, data["mode_color"] - 1, 13, 1)
if theme == "inverted":
# Add spaces on either end
icon = f" {icon} "
mode = f" {mode} "
file = f" {file} "
else:
# Subtract one from all colours
for index, color in enumerate(colors):
colors[index] -= 1
# Add spaces before each part # Add spaces before each part
icon = f" {icon}" icon = f" {data['icon']}"
mode = f" {mode}" mode = f" {data['mode'].upper()}"
file = f" {file}" file = f" {data['file']}"
else:
# The theme colors
colors = (8, data["mode_color"], 14, 2)
# Add spaces on either end
icon = f" {data['icon']} "
mode = f" {data['mode'].upper()} "
file = f" {data['file']} "
return colors, icon, mode, file
def refresh(stdscr, data):
colors, icon, mode, file = themes(data)
# Render icon # Render icon
stdscr.addstr(height - 2, 0, icon, curses.color_pair(colors[0]) | curses.A_BOLD) stdscr.addstr(data["height"] - 2, 0, icon,
curses.color_pair(colors[0]) | curses.A_BOLD)
# Render mode # Render mode
stdscr.addstr(height - 2, len(icon), mode, curses.color_pair(colors[1]) | curses.A_BOLD) stdscr.addstr(data["height"] - 2, len(icon), mode,
curses.color_pair(colors[1]) | curses.A_BOLD)
# Render file name # Render file name
stdscr.addstr(height - 2, len(icon) + len(mode), file, curses.color_pair(colors[2]) | curses.A_BOLD) stdscr.addstr(data["height"] - 2, len(icon) + len(mode), file,
curses.color_pair(colors[2]) | curses.A_BOLD)
# Rest of the bar # Rest of the bar
stdscr.addstr(height - 2, len(icon) + len(mode) + len(file), " " * (width - (len(icon) + len(mode) + len(file))), stdscr.addstr(data["height"] - 2, len(icon) + len(mode) + len(file),
" " * (data["width"] - (len(icon) + len(mode) + len(file))),
curses.color_pair(colors[3])) curses.color_pair(colors[3]))

View File

@ -1,16 +1,17 @@
from sys import exit from sys import exit
from core import cursor
import curses import curses
def goodbye(stdscr, height, width): def goodbye(stdscr, data):
# The prompt message # The prompt message
prompt = "Really quit lambda? (y or n): " prompt = "Really quit lambda? (y or n): "
# Clear the bottom line # Clear the bottom line
stdscr.addstr(height-1, 0, " " * (width - 1), curses.color_pair(1)) stdscr.addstr(data["height"]-1, 0, " " * (data["width"] - 1), curses.color_pair(1))
# Print the prompt # Print the prompt
stdscr.addstr(height-1, 0, prompt, curses.color_pair(11)) stdscr.addstr(data["height"]-1, 0, prompt, curses.color_pair(11))
# Wait for and capture a key press from the user # Wait for and capture a key press from the user
key = stdscr.getch() key = stdscr.getch()
@ -20,9 +21,17 @@ def goodbye(stdscr, height, width):
exit() exit()
# Clear the bottom line again # Clear the bottom line again
stdscr.addstr(height-1, 0, " " * (width - 1), curses.color_pair(1)) stdscr.addstr(data["height"]-1, 0, " " * (data["width"] - 1), curses.color_pair(1))
def error(stdscr, height, error_msg): def error(stdscr, data, error_msg):
error_msg = f" ERROR {error_msg}" # Print the error message to the bottom line
stdscr.addstr(height-1, 0, error_msg, curses.color_pair(3)) error_msg = f"ERROR: {error_msg}"
stdscr.addstr(data["height"]-1, 0, error_msg, curses.color_pair(3))
stdscr.addstr(data["height"]-1, len(error_msg) + 1, "(press any key) ", curses.color_pair(1))
# Wait for a key to be pressed
stdscr.getch()
# Clear the bottom line
stdscr.addstr(data["height"]-1, 0, " " * (data["width"] - 1), curses.color_pair(1))

58
lambda
View File

@ -1,8 +1,8 @@
#!/usr/bin/python #!/usr/bin/python
from core import colors, cursor, mode
import os import os
import curses import curses
from core import colors, cursor import argparse
from modes import normal
def start_screen(stdscr): def start_screen(stdscr):
@ -12,7 +12,7 @@ def start_screen(stdscr):
# Startup text # Startup text
title = "λ Lambda" title = "λ Lambda"
subtext = [ subtext = [
"A performant, efficient and hackable text editor", "Next generation hackable text editor for nerds",
"", "",
"Type :h to open the README.md document", "Type :h to open the README.md document",
"Type :o <file> to open a file and edit", "Type :o <file> to open a file and edit",
@ -33,39 +33,35 @@ def start_screen(stdscr):
stdscr.addstr(start_y, start_x, text) stdscr.addstr(start_y, start_x, text)
def start(stdscr): def start(stdscr, file):
# Initialise data before starting # Initialise data before starting
data = {"cursor_y": 0, "cursor_x": 0, "commands": []} data = {
"cursor_y": 0,
# Clear and refresh the screen for a blank canvas "cursor_x": 0,
stdscr.clear() "height": 0,
stdscr.refresh() "width": 0,
"mode": "normal",
"icon": "λ",
"file": file,
"statusbar_theme": "filled"
}
# Initialise colors # Initialise colors
colors.init_colors() colors.init_colors()
# Load the start screen
start_screen(stdscr)
# Change the cursor shape # Change the cursor shape
cursor.cursor_mode("block") cursor.cursor_mode("block")
# Start the screen
start_screen(stdscr)
# Main loop # Main loop
while True: while True:
# Get the height and width of the screen # Get the height and width of the screen
height, width = stdscr.getmaxyx() data["height"], data["width"] = stdscr.getmaxyx()
# Activate normal mode # Activate the next mode
data = normal.activate(stdscr, height, width, data) data = mode.activate(stdscr, data)
# Calculate a valid cursor position from data
cursor_x = max(2, data["cursor_x"])
cursor_x = min(width - 1, cursor_x)
cursor_y = max(0, data["cursor_y"])
cursor_y = min(height - 3, cursor_y)
# Move the cursor
stdscr.move(cursor_y, cursor_x)
# Refresh and clear the screen # Refresh and clear the screen
stdscr.refresh() stdscr.refresh()
@ -73,12 +69,24 @@ def start(stdscr):
def main(): def main():
parser = argparse.ArgumentParser(description="Process some integers.")
parser.add_argument("file", metavar="file", type=str, nargs="?",
help="File to open")
args = parser.parse_args()
# Check the file name
if args.file:
file = args.file
else:
file = "[No Name]"
# Change the escape delay to 25ms # Change the escape delay to 25ms
# Fixes an issue where esc takes too long to press # Fixes an issue where esc takes too long to press
os.environ.setdefault("ESCDELAY", "25") os.environ.setdefault("ESCDELAY", "25")
# Initialise the screen # Initialise the screen
curses.wrapper(start) curses.wrapper(start, file)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -2,7 +2,7 @@ from core import statusbar, utils
import curses import curses
def execute(stdscr, height, width, commands): def execute(stdscr, data, commands):
if not commands: if not commands:
# Quit if there are no commands, don't check anything # Quit if there are no commands, don't check anything
return return
@ -11,20 +11,32 @@ def execute(stdscr, height, width, commands):
if command == "w": if command == "w":
# Write to the file # Write to the file
pass pass
elif command == "q": elif command == "q":
# Goodbye prompt # Goodbye prompt
utils.goodbye(stdscr, height, width) utils.goodbye(stdscr, data)
elif command == "t":
if data["statusbar_theme"] == "filled":
data["statusbar_theme"] = "bare"
else:
data["statusbar_theme"] = "filled"
else:
utils.error(stdscr, data, f"Not an editor command: '{command}'")
return data
def activate(stdscr, height, width, data): def activate(stdscr, data):
# Initialise variables # Initialise variables
key = 0
commands = [] commands = []
# Visibly switch to command mode # Visibly switch to command mode
statusbar.refresh(stdscr, height, width, "COMMAND") statusbar.refresh(stdscr, data)
stdscr.addstr(height-1, 0, ":") stdscr.addstr(data["height"]-1, 0, ":")
stdscr.move(height-1, 1) stdscr.move(data["height"]-1, 1)
# Main loop # Main loop
while True: while True:
@ -34,38 +46,44 @@ def activate(stdscr, height, width, data):
# Handle subtracting a key (backspace) # Handle subtracting a key (backspace)
if key == curses.KEY_BACKSPACE: if key == curses.KEY_BACKSPACE:
# Write whitespace over characters to refresh it # Write whitespace over characters to refresh it
stdscr.addstr(height-1, 1, " " * len(commands)) stdscr.addstr(data["height"]-1, 1, " " * len(commands))
if commands: if commands:
# Subtract a character # Subtract a character
commands.pop() commands.pop()
else: else:
# If there's nothing left, quit the loop # Exit command mode and enter normal mode if there is nothing left
data["mode"] = "normal"
return data return data
elif key == 27: elif key == 27:
# Quit the loop if escape is pressed # Exit command mode and enter normal mode if "esc" is pressed
data["mode"] = "normal"
return data return data
elif key in (curses.KEY_ENTER, ord('\n'), ord('\r')): elif key in (curses.KEY_ENTER, ord('\n'), ord('\r'), ord(":"), ord(";")):
# Execute commands # Execute commands
execute(stdscr, height, width, commands) data = execute(stdscr, data, commands)
# Clear the bottom bar # Clear the bottom bar
stdscr.addstr(height - 1, 0, " " * (width - 1)) stdscr.addstr(data["height"] - 1, 0, " " * (data["width"] - 1))
# Return to normal mode after executing a command
data["mode"] = "normal"
return data return data
else: else:
# If any other key is typed, append it # If any other key is typed, append it
# As long as the key is in the valid list # As long as the key is in the valid list
valid = "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ!" valid = "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ!"
if chr(key) in valid and len(commands) < (width - 2): if chr(key) in valid and len(commands) < (data["width"] - 2):
commands.append(chr(key)) commands.append(chr(key))
# Join the commands together for visibility on the screen # Join the commands together for visibility on the screen
friendly_command = "".join(commands) friendly_command = "".join(commands)
# Write the commands to the screen # Write the commands to the screen
stdscr.addstr(height-1, 1, friendly_command) stdscr.addstr(data["height"]-1, 1, friendly_command)
# Move the cursor the end of the commands # Move the cursor the end of the commands
stdscr.move(height-1, len(commands)+1) stdscr.move(data["height"]-1, len(commands)+1)

View File

@ -1,17 +1,33 @@
from core import statusbar from core import statusbar, cursor
def execute(stdscr, height, width, key): def execute(data, key):
return return data
def activate(stdscr, height, width, data): def activate(stdscr, data):
# Refresh the status bar with a different colour for insert # Refresh the status bar with a different colour for insert
colors = [8, 12, 14, 2] data["statusbar_colors"] = [8, 12, 14, 2]
statusbar.refresh(stdscr, height, width, "INSERT", colors) statusbar.refresh(stdscr, data)
# Refresh the status bar
statusbar.refresh(stdscr, data)
# Move the cursor
cursor.move(stdscr, data)
# Switch to a line cursor
cursor.cursor_mode("line")
while True:
# Wait for and capture a key press from the user # Wait for and capture a key press from the user
key = stdscr.getch() key = stdscr.getch()
# Exit insert mode
if key == 27:
data["mode"] = "normal"
return data
# Check keybindings
data = execute(data, key)
return data return data

View File

@ -1,9 +1,7 @@
from modes import command from core import statusbar, cursor
from modes import insert
from core import statusbar
def execute(stdscr, height, width, key, data): def execute(data, key):
if key == ord("j"): if key == ord("j"):
# Move the cursor down # Move the cursor down
data["cursor_y"] += 1 data["cursor_y"] += 1
@ -21,23 +19,30 @@ def execute(stdscr, height, width, key, data):
data["cursor_x"] -= 1 data["cursor_x"] -= 1
elif key == ord("i"): elif key == ord("i"):
# Insert mode # Exit normal mode and enter insert mode
data = insert.activate(stdscr, height, width, data) data["mode"] = "insert"
elif key in (ord(":"), ord(";")): elif key in (ord(":"), ord(";")):
# Switch to command mode # Exit normal mode and enter command mode
data = command.activate(stdscr, height, width, data) data["mode"] = "command"
return data return data
def activate(stdscr, height, width, data): def activate(stdscr, data):
# Refresh the status bar # Refresh the status bar
statusbar.refresh(stdscr, height, width, "NORMAL") statusbar.refresh(stdscr, data)
# Move the cursor
cursor.move(stdscr, data)
# Switch the cursor to a block
cursor.cursor_mode("block")
# Wait for and capture a key press from the user # Wait for and capture a key press from the user
key = stdscr.getch() key = stdscr.getch()
# Check against the keybindings # Check against the keybindings
data = execute(stdscr, height, width, key, data) data = execute(data, key)
return data return data