diff --git a/core/buffers.py b/core/buffers.py index 19501de..0fef9c0 100644 --- a/core/buffers.py +++ b/core/buffers.py @@ -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 diff --git a/core/components.py b/core/components.py index 53e7551..007811d 100644 --- a/core/components.py +++ b/core/components.py @@ -22,7 +22,7 @@ class StatusBar: class Components: def __init__(self, components: dict = None): self.components = components or { - "left": [[" "], ["12222"], [""]], + "left": [" "], "bottom": [StatusBar], } diff --git a/core/cursors.py b/core/cursors.py index 59a75c2..d38dea1 100644 --- a/core/cursors.py +++ b/core/cursors.py @@ -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"])) diff --git a/core/modes.py b/core/modes.py index 23a9305..43f7279 100644 --- a/core/modes.py +++ b/core/modes.py @@ -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 diff --git a/core/utils.py b/core/utils.py index 6b620c6..e913205 100644 --- a/core/utils.py +++ b/core/utils.py @@ -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)) diff --git a/main.py b/main.py index caca69c..d21255a 100644 --- a/main.py +++ b/main.py @@ -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 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: diff --git a/mode/command.py b/mode/command.py index 901ce78..9500aca 100644 --- a/mode/command.py +++ b/mode/command.py @@ -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): diff --git a/mode/normal.py b/mode/normal.py index 00ed1e2..9c19789 100644 --- a/mode/normal.py +++ b/mode/normal.py @@ -1,6 +1,4 @@ from core import cursors, modes -from mode import insert -from mode import command def execute(instance, key):