🤯 Complete reworking of cursor system & fixes!

This commit is contained in:
Maddie H 2022-04-14 18:24:45 +01:00
parent 612c706677
commit 0d06c65a13
8 changed files with 81 additions and 71 deletions

View File

@ -1,4 +1,3 @@
from core import cursors
import os import os
@ -9,13 +8,10 @@ class Buffer:
self.data = data or [""] self.data = data or [""]
def render(self, instance): def render(self, instance):
# Update the screen information
instance.update()
for y, line in enumerate(self.data[instance.offset[0]:]): for y, line in enumerate(self.data[instance.offset[0]:]):
if y < instance.safe_height: if y <= instance.safe_height:
for x, character in enumerate(line[instance.offset[1]:]): for x, character in enumerate(line[instance.offset[1]:]):
if x < instance.safe_width: if x <= instance.safe_width:
instance.screen.addstr(y, x + instance.components.get_component_width( instance.screen.addstr(y, x + instance.components.get_component_width(
instance.components.components["left"]), character) instance.components.components["left"]), character)
@ -41,6 +37,10 @@ def open_file(file_name):
# Convert it into a list of lines # Convert it into a list of lines
lines = f.readlines() lines = f.readlines()
# Add a line if the file is empty or if the last line is not empty
if lines[-1].endswith("\n") or not len(lines):
lines.append("")
# Return the list of lines # Return the list of lines
return lines return lines

View File

@ -22,7 +22,7 @@ class StatusBar:
class Components: class Components:
def __init__(self, components: dict = None): def __init__(self, components: dict = None):
self.components = components or { self.components = components or {
"left": [[" "], ["12222"], [""]], "left": [" "],
"bottom": [StatusBar], "bottom": [StatusBar],
} }

View File

@ -1,3 +1,4 @@
from core import utils
import curses import curses
@ -15,60 +16,54 @@ def mode(to_mode: str):
curses.curs_set(1) curses.curs_set(1)
# TODO
def push(instance, direction: (int, str)): def push(instance, direction: (int, str)):
if direction in (0, "up", "north"): if direction in (0, "up", "north"):
# If the cursor is at the top of the file # If the cursor isn't at the top of the screen
if instance.cursor[0] == 0 and not instance.offset[0] == 0: if instance.raw_cursor[0] > 0:
# Move the buffer up # Move the cursor up
instance.raw_cursor[0] -= 1
# Move the buffer upwards if the cursor is at the top of the screen and not at the top of the buffer
if instance.raw_cursor[0] == 0 and instance.cursor[0] == instance.offset[0] and instance.cursor[0] != 0:
instance.offset[0] -= 1 instance.offset[0] -= 1
elif instance.cursor[0] != 0:
# Decrease the y position of the cursor
instance.cursor[0] -= 1
# Jump to the end of the line
if instance.cursor[1] > len(instance.buffer.data[instance.current_line - 1]) - 2:
instance.cursor[1] = len(instance.buffer.data[instance.current_line - 1]) - 2
elif direction in (1, "right", "east"): elif direction in (1, "right", "east"):
# Increase the x position of the cursor instance.raw_cursor[1] += 1
if instance.cursor[1] < len(instance.buffer.data[instance.current_line]) - 2:
instance.cursor[1] += 1
elif direction in (2, "down", "south"): elif direction in (2, "down", "south"):
# Check if the cursor is at the bottom of the screen if instance.raw_cursor[0] == instance.safe_height and instance.cursor[0] != len(instance.buffer.data) - 1:
if instance.cursor[0] == instance.safe_height - 2 and not instance.current_line == len(instance.buffer.data):
# Move the buffer down
instance.offset[0] += 1 instance.offset[0] += 1
elif instance.cursor[0] != instance.safe_height - 2: # If the cursor isn't at the bottom of the screen
# Increase the y position of the cursor elif instance.raw_cursor[0] != instance.safe_height and instance.cursor[0] != len(instance.buffer.data) - 1:
instance.cursor[0] += 1 # Move the cursor down
instance.raw_cursor[0] += 1
# Jump to the end of the line
if instance.cursor[1] > len(instance.buffer.data[instance.current_line + 1]) - 2:
instance.cursor[1] = len(instance.buffer.data[instance.current_line + 1]) - 2
elif direction in (3, "left", "west"): elif direction in (3, "left", "west"):
# Decrease the x position of the cursor if instance.raw_cursor[1] > 0:
if instance.cursor[1] != 0: instance.raw_cursor[1] -= 1
instance.cursor[1] -= 1
def check(instance, cursor: list): def check(instance, cursor: list) -> list:
# Prevent any values out of bounds (especially important when resizing) # Prevent the cursor from going outside the buffer
cursor[1] = max(0, cursor[1]) cursor[1] = min(len(instance.buffer.data[instance.cursor[0]]) - 2, cursor[1])
cursor[1] = min(instance.safe_width - 1, cursor[1])
# Prevent any negative values
cursor[0] = max(0, cursor[0]) cursor[0] = max(0, cursor[0])
cursor[0] = min(instance.height - 2 - len(instance.components.components["bottom"]), cursor[0]) cursor[1] = max(0, cursor[1])
# Prevent the cursor from going outside the screen
cursor[1] = min(instance.safe_width, cursor[1])
cursor[0] = min(instance.safe_height, cursor[0])
return cursor return cursor
def move(instance): def move(instance):
# Run a final check to see if the cursor is valid # Run a final check to see if the cursor is valid
instance.cursor = check(instance, instance.cursor) instance.raw_cursor = check(instance, instance.raw_cursor)
# Moves the cursor to anywhere on the screen # Moves the cursor to anywhere on the screen
instance.screen.move(instance.cursor[0], instance.cursor[1] + instance.screen.move(instance.raw_cursor[0], instance.raw_cursor[1] +
instance.components.get_component_width(instance.components.components["left"])) instance.components.get_component_width(instance.components.components["left"]))

View File

@ -4,7 +4,9 @@ from mode import normal, insert, command
def activate(instance, mode): def activate(instance, mode):
# Visibly update the mode # Visibly update the mode
instance.mode = mode instance.mode = mode
instance.update()
# Refresh the screen
instance.refresh()
if mode == "command": if mode == "command":
# Activate command mode # Activate command mode

View File

@ -7,6 +7,14 @@ import sys
import os import os
def gracefully_exit():
# Close the curses window
curses.endwin()
# Finally, exit the program
sys.exit()
def load_json(file: str) -> dict: def load_json(file: str) -> dict:
# Load the json file with read permissions # Load the json file with read permissions
with open(file, "r") as f: with open(file, "r") as f:
@ -69,7 +77,7 @@ def prompt(instance, message: str, color: int = 1) -> (list, None):
# Subtracting a key (backspace) # Subtracting a key (backspace)
if key in (curses.KEY_BACKSPACE, 127, '\b'): if key in (curses.KEY_BACKSPACE, 127, '\b'):
# Write whitespace over characters to refresh it # Write whitespace over characters to refresh it
clear(instance, instance.height - 1, 0) clear(instance, instance.height - 1, len(message) + len(inp) - 1)
if inp: if inp:
# Subtract a character from the input list # Subtract a character from the input list
@ -94,6 +102,12 @@ def prompt(instance, message: str, color: int = 1) -> (list, None):
if chr(key) in valid and len(inp) < (instance.width - 2): if chr(key) in valid and len(inp) < (instance.width - 2):
inp.append(chr(key)) inp.append(chr(key))
# Refresh the screen
instance.screen.refresh()
# Refresh the screen
instance.refresh()
# Write the message to the screen # Write the message to the screen
instance.screen.addstr(instance.height - 1, 0, message, curses.color_pair(color)) instance.screen.addstr(instance.height - 1, 0, message, curses.color_pair(color))

46
main.py
View File

@ -1,41 +1,44 @@
import argparse
import curses
import os
from core import colors, cursors, buffers, modes, utils from core import colors, cursors, buffers, modes, utils
from core.buffers import Buffer from core.buffers import Buffer
from core.components import Components from core.components import Components
import argparse
import curses
import sys
import os
class Lambda: class Lambda:
def __init__(self, buffer: Buffer, config: dict = None): def __init__(self, buffer: Buffer, config: dict = None):
self.screen = curses.initscr() self.screen = curses.initscr()
self.buffer = buffer
self.cursor = [0, 0]
self.offset = [0, 0]
self.current_line = 0
self.mode = "normal"
self.components = Components() self.components = Components()
self.config = config or {"icon": "λ"}
self.buffer = buffer
self.mode = "normal"
self.cursor = [0, 0]
self.raw_cursor = [0, 0]
self.offset = [0, 0]
self.height = 0 self.height = 0
self.width = 0 self.width = 0
self.safe_height = 0 self.safe_height = 0
self.safe_width = 0 self.safe_width = 0
self.config = config or {"icon": "λ"}
def update_dimensions(self): def update_dimensions(self):
# Calculate the entire height and width of the terminal # Calculate the entire height and width of the terminal
self.height, self.width = self.screen.getmaxyx() self.height, self.width = self.screen.getmaxyx()
# Calculate the safe area for the buffer by removing heights & widths of components # Calculate the safe area for the buffer by removing heights & widths of components
self.safe_height = self.height - len(self.components.components["bottom"]) self.safe_height = self.height - len(self.components.components["bottom"]) - 2
self.safe_width = self.width - self.components.get_component_width(self.components.components["left"]) self.safe_width = self.width - self.components.get_component_width(self.components.components["left"]) - 1
def refresh(self):
# Calculate the real cursor position
self.cursor[0], self.cursor[1] = self.raw_cursor[0] + self.offset[0], self.raw_cursor[1] + self.offset[1]
def update(self):
# Update the dimensions of the terminal # Update the dimensions of the terminal
self.update_dimensions() self.update_dimensions()
# Calculate the current line number # Write the buffer to the screen
self.current_line = self.cursor[0] + self.offset[0] self.buffer.render(self)
# Refresh the on-screen components # Refresh the on-screen components
self.components.render(self) self.components.render(self)
@ -67,11 +70,8 @@ class Lambda:
def run(self): def run(self):
# The main loop, which runs until the user quits # The main loop, which runs until the user quits
while True: while True:
# Write the buffer to the screen
self.buffer.render(self)
# Update the screen variables # Update the screen variables
self.update() self.refresh()
# Wait for a keypress # Wait for a keypress
key = self.screen.getch() key = self.screen.getch()
@ -81,7 +81,7 @@ class Lambda:
# Refresh and clear the screen # Refresh and clear the screen
self.screen.refresh() self.screen.refresh()
self.screen.clear() self.screen.erase()
def main(): def main():
@ -108,11 +108,7 @@ def main():
# KeyboardInterrupt is thrown when <C-c> is pressed (exit) # KeyboardInterrupt is thrown when <C-c> is pressed (exit)
except KeyboardInterrupt: except KeyboardInterrupt:
# Clean up the screen utils.gracefully_exit()
curses.endwin()
# Then, just exit
sys.exit()
# Excepts *any* errors that occur # Excepts *any* errors that occur
except Exception as exception: except Exception as exception:

View File

@ -11,6 +11,11 @@ def execute(instance, commands):
# Write to the file # Write to the file
pass pass
elif command == "d":
# Load a prompt with debug info
utils.prompt(instance, f"Cursor: {instance.cursor} | Raw: {instance.raw_cursor} | Len: {len(instance.buffer.data)}")
utils.prompt(instance, f"{len(instance.buffer.data[6])}")
# Quit # Quit
elif command == "q": elif command == "q":
# Load a goodbye prompt # Load a goodbye prompt
@ -18,7 +23,7 @@ def execute(instance, commands):
# Unknown command # Unknown command
else: else:
utils.error(instance, f"not an editor command: '{command}'") utils.error(instance, f"invalid command: '{command}'")
def activate(instance): def activate(instance):

View File

@ -1,6 +1,4 @@
from core import cursors, modes from core import cursors, modes
from mode import insert
from mode import command
def execute(instance, key): def execute(instance, key):