🤯 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
@ -9,13 +8,10 @@ class Buffer:
self.data = data or [""]
def render(self, instance):
# Update the screen information
instance.update()
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]:]):
if x < instance.safe_width:
if x <= instance.safe_width:
instance.screen.addstr(y, x + instance.components.get_component_width(
instance.components.components["left"]), character)
@ -41,6 +37,10 @@ def open_file(file_name):
# Convert it into a list of lines
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 lines

View File

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

View File

@ -1,3 +1,4 @@
from core import utils
import curses
@ -15,60 +16,54 @@ def mode(to_mode: str):
curses.curs_set(1)
# TODO
def push(instance, direction: (int, str)):
if direction in (0, "up", "north"):
# If the cursor is at the top of the file
if instance.cursor[0] == 0 and not instance.offset[0] == 0:
# Move the buffer up
# If the cursor isn't at the top of the screen
if instance.raw_cursor[0] > 0:
# 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
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"):
# Increase the x position of the cursor
if instance.cursor[1] < len(instance.buffer.data[instance.current_line]) - 2:
instance.cursor[1] += 1
instance.raw_cursor[1] += 1
elif direction in (2, "down", "south"):
# Check if the cursor is at the bottom of the screen
if instance.cursor[0] == instance.safe_height - 2 and not instance.current_line == len(instance.buffer.data):
# Move the buffer down
if instance.raw_cursor[0] == instance.safe_height and instance.cursor[0] != len(instance.buffer.data) - 1:
instance.offset[0] += 1
elif instance.cursor[0] != instance.safe_height - 2:
# Increase 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
# If the cursor isn't at the bottom of the screen
elif instance.raw_cursor[0] != instance.safe_height and instance.cursor[0] != len(instance.buffer.data) - 1:
# Move the cursor down
instance.raw_cursor[0] += 1
elif direction in (3, "left", "west"):
# Decrease the x position of the cursor
if instance.cursor[1] != 0:
instance.cursor[1] -= 1
if instance.raw_cursor[1] > 0:
instance.raw_cursor[1] -= 1
def check(instance, cursor: list):
# Prevent any values out of bounds (especially important when resizing)
cursor[1] = max(0, cursor[1])
cursor[1] = min(instance.safe_width - 1, cursor[1])
def check(instance, cursor: list) -> list:
# Prevent the cursor from going outside the buffer
cursor[1] = min(len(instance.buffer.data[instance.cursor[0]]) - 2, cursor[1])
# Prevent any negative values
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
def move(instance):
# 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
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"]))

View File

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

View File

@ -7,6 +7,14 @@ import sys
import os
def gracefully_exit():
# Close the curses window
curses.endwin()
# Finally, exit the program
sys.exit()
def load_json(file: str) -> dict:
# Load the json file with read permissions
with open(file, "r") as f:
@ -69,7 +77,7 @@ def prompt(instance, message: str, color: int = 1) -> (list, None):
# Subtracting a key (backspace)
if key in (curses.KEY_BACKSPACE, 127, '\b'):
# 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:
# 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):
inp.append(chr(key))
# Refresh the screen
instance.screen.refresh()
# Refresh the screen
instance.refresh()
# Write the message to the screen
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.buffers import Buffer
from core.components import Components
import argparse
import curses
import sys
import os
class Lambda:
def __init__(self, buffer: Buffer, config: dict = None):
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.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.width = 0
self.safe_height = 0
self.safe_width = 0
self.config = config or {"icon": "λ"}
def update_dimensions(self):
# Calculate the entire height and width of the terminal
self.height, self.width = self.screen.getmaxyx()
# 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_width = self.width - self.components.get_component_width(self.components.components["left"])
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"]) - 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
self.update_dimensions()
# Calculate the current line number
self.current_line = self.cursor[0] + self.offset[0]
# Write the buffer to the screen
self.buffer.render(self)
# Refresh the on-screen components
self.components.render(self)
@ -67,11 +70,8 @@ class Lambda:
def run(self):
# The main loop, which runs until the user quits
while True:
# Write the buffer to the screen
self.buffer.render(self)
# Update the screen variables
self.update()
self.refresh()
# Wait for a keypress
key = self.screen.getch()
@ -81,7 +81,7 @@ class Lambda:
# Refresh and clear the screen
self.screen.refresh()
self.screen.clear()
self.screen.erase()
def main():
@ -108,11 +108,7 @@ def main():
# KeyboardInterrupt is thrown when <C-c> is pressed (exit)
except KeyboardInterrupt:
# Clean up the screen
curses.endwin()
# Then, just exit
sys.exit()
utils.gracefully_exit()
# Excepts *any* errors that occur
except Exception as exception:

View File

@ -11,6 +11,11 @@ def execute(instance, commands):
# Write to the file
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
elif command == "q":
# Load a goodbye prompt
@ -18,7 +23,7 @@ def execute(instance, commands):
# Unknown command
else:
utils.error(instance, f"not an editor command: '{command}'")
utils.error(instance, f"invalid command: '{command}'")
def activate(instance):

View File

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