rust branch
This commit is contained in:
parent
9126abc6d5
commit
ca0938b523
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,4 +1,2 @@
|
||||
__pycache__
|
||||
core/__pycache__
|
||||
mode/__pycache__
|
||||
.idea
|
||||
.idea
|
||||
target
|
||||
|
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal file
@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "lambda"
|
||||
version = "0.1.0"
|
8
Cargo.toml
Normal file
8
Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "lambda"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
35
README.md
35
README.md
@ -1,35 +0,0 @@
|
||||
# λ lambda
|
||||
|
||||
Next generation hackable text editor for nerds.
|
||||
|
||||
### Let it be known!
|
||||
|
||||
Lambda is in *very* early stages at the moment. Features may change completely, or even be removed.<br>
|
||||
Don't expect lambda to stay the way it is. Updates are pushed often.
|
||||
|
||||
### Overview
|
||||
|
||||
Lambda is a similar text editor to `vim` or `kakoune`.<br>
|
||||
However, it takes a different approach to most of the features seen in other editors.
|
||||
|
||||
- Lambda is written in Python, so it is easy to hack and learn.
|
||||
- It also has a good amount of comments!
|
||||
- Lambda is incredibly modular, so you can easily add new features.
|
||||
- Lambda follows the unix philosophy of "do one thing and do it well."
|
||||
- It has no bloated features, like splits or tabs
|
||||
- It contains the bare necessities and provides a few extra modules
|
||||
- Lambda isn't limited to modes or keybindings.
|
||||
- Keybindings and modes can be easily changed
|
||||
- Other modes can be used by holding down keybindings (i.e. `ctrl-x` inside of `insert` mode)
|
||||
- Lambda is extremely fast and makes use of efficient memory management.
|
||||
- Neovim is slow, and actually requires [a plugin to speed it up](https://github.com/lewis6991/impatient.nvim).
|
||||
- Lambda has much better default keybindings than other text editors.
|
||||
|
||||
### Getting started
|
||||
|
||||
```bash
|
||||
git clone https://github.com/SpyHoodle/lambda.git # Clone the repository
|
||||
cd lambda # Enter lambda directory
|
||||
chmod +x install.sh # Make the install script executable
|
||||
./install.sh # Run the install script
|
||||
```
|
@ -1,90 +0,0 @@
|
||||
import os
|
||||
|
||||
|
||||
class Buffer:
|
||||
def __init__(self, path: str, name: str = None, data: list = None):
|
||||
self.path = path
|
||||
self.name = name or "[No Name]"
|
||||
self.data = data or [""]
|
||||
|
||||
def render(self, instance):
|
||||
for y, line in enumerate(self.data[instance.offset[0]:]):
|
||||
if y <= instance.safe_height:
|
||||
for x, character in enumerate(line[instance.offset[1]:]):
|
||||
if x <= instance.safe_width:
|
||||
instance.screen.addstr(y, x + instance.components.get_component_width(
|
||||
instance.components.components["left"]), character)
|
||||
|
||||
# Write blank spaces for the rest of the line
|
||||
if instance.safe_width - len(line) > 0:
|
||||
instance.screen.addstr(y, instance.components.get_component_width(
|
||||
instance.components.components["left"]) + len(line), " " * (instance.safe_width - len(line)))
|
||||
|
||||
@staticmethod
|
||||
def delete_line(instance, y: int = None):
|
||||
# Default to the cursor position
|
||||
y = y or instance.cursor[0]
|
||||
|
||||
# Remove a line from the buffer
|
||||
instance.buffer.data.pop(y)
|
||||
|
||||
@staticmethod
|
||||
def insert_line(instance, y: int = None):
|
||||
# Default to the cursor position
|
||||
y = y or instance.cursor[0]
|
||||
|
||||
# Insert a line into the buffer
|
||||
instance.buffer.data.insert(y, "")
|
||||
|
||||
@staticmethod
|
||||
def delete_char(instance, y: int = None, x: int = None):
|
||||
# Default to the cursor position
|
||||
y = y or instance.cursor[0]
|
||||
x = x or instance.cursor[1]
|
||||
|
||||
# Remove a character from the line at a given index
|
||||
instance.buffer.data[y] = instance.buffer.data[y][:x - 1] + instance.buffer.data[y][x:]
|
||||
|
||||
@staticmethod
|
||||
def insert_char(instance, char: (str, chr), y: int = None, x: int = None):
|
||||
# Default to the cursor position
|
||||
y = y or instance.cursor[0]
|
||||
x = x or instance.cursor[1]
|
||||
|
||||
# Insert a character into the line at a given index
|
||||
instance.buffer.data[y] = instance.buffer.data[y][:x] + char + instance.buffer.data[y][x:]
|
||||
|
||||
|
||||
def open_file(file_path):
|
||||
# Open the file
|
||||
with open(file_path) as f:
|
||||
# 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("")
|
||||
|
||||
# Remove the newlines
|
||||
lines = [line.rstrip("\n") for line in lines]
|
||||
|
||||
# Return the list of lines
|
||||
return lines
|
||||
|
||||
|
||||
def load_file(file_path=None):
|
||||
# Default settings for a file
|
||||
file_name = "[No Name]"
|
||||
file_data = [""]
|
||||
|
||||
if file_path:
|
||||
# Set the file's name
|
||||
file_name = os.path.basename(file_path)
|
||||
|
||||
# Only if the file actually exists
|
||||
if os.path.exists(file_path):
|
||||
# Open the file as a list of lines
|
||||
file_data = open_file(file_path)
|
||||
|
||||
# Return a dictionary which will become all the data about the buffer
|
||||
return Buffer(file_path, file_name, file_data)
|
@ -1,51 +0,0 @@
|
||||
import curses
|
||||
|
||||
|
||||
class Codes:
|
||||
# Color codes
|
||||
red = '\033[91m'
|
||||
green = '\033[92m'
|
||||
blue = '\033[94m'
|
||||
yellow = '\033[93m'
|
||||
cyan = '\033[96m'
|
||||
magenta = '\033[95m'
|
||||
white = '\033[97m'
|
||||
selected_white = '\033[47m'
|
||||
selected_green = '\033[42m'
|
||||
strike = '\033[9m'
|
||||
italic = '\033[3m'
|
||||
end = '\033[0m'
|
||||
|
||||
|
||||
def init_colors():
|
||||
# Activate color support
|
||||
curses.start_color()
|
||||
|
||||
# Foreground: WHITE, Background: BLACK
|
||||
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK)
|
||||
# Foreground: BLACK, Background: WHITE
|
||||
curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_WHITE)
|
||||
# Foreground: RED, Background: BLACK
|
||||
curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK)
|
||||
# Foreground: BLACK, Background: RED
|
||||
curses.init_pair(4, curses.COLOR_BLACK, curses.COLOR_RED)
|
||||
# Foreground: GREEN, Background: BLACK
|
||||
curses.init_pair(5, curses.COLOR_GREEN, curses.COLOR_BLACK)
|
||||
# Foreground: BLACK, Background: GREEN
|
||||
curses.init_pair(6, curses.COLOR_BLACK, curses.COLOR_GREEN)
|
||||
# Foreground: YELLOW, Background: BLACK
|
||||
curses.init_pair(7, curses.COLOR_YELLOW, curses.COLOR_BLACK)
|
||||
# Foreground: BLACK, Background: YELLOW
|
||||
curses.init_pair(8, curses.COLOR_BLACK, curses.COLOR_YELLOW)
|
||||
# Foreground: CYAN, Background: BLACK
|
||||
curses.init_pair(9, curses.COLOR_CYAN, curses.COLOR_BLACK)
|
||||
# Foreground: BLACK, Background: CYAN
|
||||
curses.init_pair(10, curses.COLOR_BLACK, curses.COLOR_CYAN)
|
||||
# Foreground: BLUE, Background: BLACK
|
||||
curses.init_pair(11, curses.COLOR_BLUE, curses.COLOR_BLACK)
|
||||
# Foreground: BLACK, Background: BLUE
|
||||
curses.init_pair(12, curses.COLOR_BLACK, curses.COLOR_BLUE)
|
||||
# Foreground: MAGENTA, Background: BLACK
|
||||
curses.init_pair(13, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
|
||||
# Foreground: BLACK, Background: MAGENTA
|
||||
curses.init_pair(14, curses.COLOR_BLACK, curses.COLOR_MAGENTA)
|
@ -1,73 +0,0 @@
|
||||
import curses
|
||||
|
||||
from core import utils
|
||||
|
||||
|
||||
class StatusBar:
|
||||
def __init__(self, instance):
|
||||
self.mode = instance.mode.upper()
|
||||
self.file = instance.buffer.name or "[No Name]"
|
||||
self.icon = instance.config["icon"] or "λ"
|
||||
self.theme = "default"
|
||||
self.colors = [7, 5, 13]
|
||||
self.components = [self.icon, self.mode, self.file]
|
||||
|
||||
def update(self, instance):
|
||||
self.mode = instance.mode.upper()
|
||||
self.components = [self.icon, self.mode, self.file]
|
||||
|
||||
def render(self, instance):
|
||||
# Clear the status bar
|
||||
utils.clear_line(instance, instance.height - 2, 0)
|
||||
|
||||
# Update variables
|
||||
self.update(instance)
|
||||
|
||||
if self.theme == "inverted":
|
||||
# Initialise the x position for each component
|
||||
x = 1
|
||||
|
||||
# Render each component
|
||||
for count, component in enumerate(self.components):
|
||||
instance.screen.addstr(instance.height - 2, x, component,
|
||||
curses.color_pair(self.colors[count]) | curses.A_BOLD)
|
||||
x += len(component) + 1
|
||||
|
||||
else:
|
||||
# Initialise temporary colors for inverted theme
|
||||
colors = []
|
||||
|
||||
# Add 1 to each color temporarily
|
||||
for color in self.colors:
|
||||
colors.append(color + 1)
|
||||
|
||||
# Initialise the x position for each component
|
||||
x = 0
|
||||
|
||||
# Render each component
|
||||
for count, component in enumerate(self.components):
|
||||
component = f" {component} "
|
||||
instance.screen.addstr(instance.height - 2, x, component,
|
||||
curses.color_pair(colors[count]) | curses.A_BOLD)
|
||||
x += len(component)
|
||||
|
||||
# Add a space at the end of the status bar
|
||||
instance.screen.addstr(instance.height - 2, x, " " * (instance.width - x),
|
||||
curses.color_pair(2))
|
||||
|
||||
|
||||
class Components:
|
||||
def __init__(self, instance, components: dict = None):
|
||||
self.components = components or {
|
||||
"left": [" "],
|
||||
"bottom": [StatusBar(instance)],
|
||||
}
|
||||
curses.endwin()
|
||||
|
||||
@staticmethod
|
||||
def get_component_width(component: list) -> int:
|
||||
return len(max(component))
|
||||
|
||||
def render(self, instance):
|
||||
for component in self.components["bottom"]:
|
||||
component.render(instance)
|
@ -1,68 +0,0 @@
|
||||
import curses
|
||||
|
||||
|
||||
def mode(to_mode: str):
|
||||
if to_mode == "block":
|
||||
print("\033[2 q")
|
||||
|
||||
elif to_mode == "line":
|
||||
print("\033[6 q")
|
||||
|
||||
elif to_mode == "hidden":
|
||||
curses.curs_set(0)
|
||||
|
||||
elif to_mode == "visible":
|
||||
curses.curs_set(1)
|
||||
|
||||
|
||||
def push(instance, direction: (int, str)):
|
||||
if direction in (0, "up", "north"):
|
||||
# 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 direction in (2, "down", "south"):
|
||||
if instance.raw_cursor[0] == instance.safe_height and instance.cursor[0] != len(instance.buffer.data) - 1:
|
||||
instance.offset[0] += 1
|
||||
|
||||
# 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 (1, "right", "east"):
|
||||
# Move the cursor one to the right
|
||||
instance.raw_cursor[1] += 1
|
||||
|
||||
elif direction in (3, "left", "west"):
|
||||
# Move the cursor one to the left
|
||||
instance.raw_cursor[1] -= 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]]) - 1, cursor[1])
|
||||
|
||||
# Prevent any negative values
|
||||
cursor[0] = max(0, 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.raw_cursor = check(instance, instance.raw_cursor)
|
||||
|
||||
# Moves the cursor to anywhere on the screen
|
||||
instance.screen.move(instance.raw_cursor[0], instance.raw_cursor[1] +
|
||||
instance.components.get_component_width(instance.components.components["left"]))
|
@ -1,34 +0,0 @@
|
||||
from mode import normal, insert, command
|
||||
|
||||
|
||||
def activate(instance, mode):
|
||||
# Visibly update the mode
|
||||
instance.mode = mode
|
||||
|
||||
# Refresh the screen
|
||||
instance.refresh()
|
||||
|
||||
if mode == "command":
|
||||
# Activate command mode
|
||||
instance.components.components["bottom"][0].colors[1] = 5
|
||||
command.activate(instance)
|
||||
|
||||
elif mode == "insert":
|
||||
# Activate insert mode
|
||||
instance.components.components["bottom"][0].colors[1] = 11
|
||||
insert.activate()
|
||||
|
||||
elif mode == "normal":
|
||||
# Activate normal mode
|
||||
instance.components.components["bottom"][0].colors[1] = 5
|
||||
normal.activate()
|
||||
|
||||
|
||||
def handle_key(instance, key):
|
||||
# Normal mode - default keybindings
|
||||
if instance.mode == "normal":
|
||||
normal.execute(instance, key)
|
||||
|
||||
# Insert mode - inserting text to the buffer
|
||||
elif instance.mode == "insert":
|
||||
insert.execute(instance, key)
|
209
core/utils.py
209
core/utils.py
@ -1,209 +0,0 @@
|
||||
import curses
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
|
||||
from core import cursors
|
||||
from core.colors import Codes as Col
|
||||
|
||||
|
||||
def gracefully_exit():
|
||||
# Close the curses window
|
||||
curses.endwin()
|
||||
|
||||
# Finally, exit the program
|
||||
sys.exit()
|
||||
|
||||
|
||||
def clear_line(instance, y: int, x: int):
|
||||
# Clear the line at the screen at position y, x
|
||||
instance.screen.insstr(y, x, " " * (instance.width - x))
|
||||
|
||||
|
||||
def pause_screen(message: str):
|
||||
# End the curses session
|
||||
curses.endwin()
|
||||
|
||||
# Print the message and wait for enter key
|
||||
input(f"{message}\n\n Press enter to continue...")
|
||||
|
||||
|
||||
def load_file(file_path: str) -> dict:
|
||||
# load the json file with read permissions
|
||||
with open(file_path, "r") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def save_file(instance, file_path: str, data: list):
|
||||
# Save the data to the file
|
||||
with open(file_path, "w") as f:
|
||||
try:
|
||||
# For each line in the file
|
||||
for index, line in enumerate(data):
|
||||
if index == len(data) - 1:
|
||||
# If this is the last line, write it without a newline
|
||||
f.write(line)
|
||||
|
||||
else:
|
||||
# Otherwise, write the line with a newline
|
||||
f.write(f"{line}\n")
|
||||
|
||||
except (OSError, IOError):
|
||||
# If the file could not be written, show an error message
|
||||
error(instance, f"File {file_path} could not be saved.")
|
||||
|
||||
|
||||
def load_config_file() -> dict:
|
||||
# Parse the path of the config file
|
||||
config_file_path = f"{Path.home()}/.config/lambda/config.json"
|
||||
|
||||
# Only if the config file exists, attempt to load it
|
||||
if os.path.exists(config_file_path):
|
||||
# Return the loaded config
|
||||
return load_file(config_file_path)
|
||||
|
||||
|
||||
def welcome(instance):
|
||||
# Startup text
|
||||
title = "λ Lambda"
|
||||
subtext = [
|
||||
"Next generation hackable text editor for nerds",
|
||||
"",
|
||||
"Type :h to open the README.md document",
|
||||
"Type :o <file> to open a file and edit",
|
||||
"Type :q or <C-c> to quit lambda.py"
|
||||
]
|
||||
|
||||
# Centering calculations
|
||||
start_x_title = int((instance.safe_width // 2) - (len(title) // 2) - len(title) % 2 + 2)
|
||||
start_y = int((instance.safe_height // 2) - 1)
|
||||
|
||||
# Rendering title
|
||||
instance.screen.addstr(start_y, start_x_title, title, curses.color_pair(7) | curses.A_BOLD)
|
||||
|
||||
# Print the subtext
|
||||
for text in subtext:
|
||||
start_y += 1
|
||||
start_x = int((instance.safe_width // 2) - (len(text) // 2) - len(text) % 2 + 2)
|
||||
instance.screen.addstr(start_y, start_x, text)
|
||||
|
||||
|
||||
def prompt(instance, message: str, color: int = 1) -> (list, None):
|
||||
# Initialise the input list
|
||||
inp = []
|
||||
|
||||
# Write whitespace over characters to refresh it
|
||||
clear_line(instance, instance.height - 1, len(message) + len(inp) - 1)
|
||||
|
||||
# Write the message to the screen
|
||||
instance.screen.addstr(instance.height - 1, 0, message, curses.color_pair(color))
|
||||
|
||||
while True:
|
||||
# Wait for a keypress
|
||||
key = instance.screen.getch()
|
||||
|
||||
# Subtracting a key (backspace)
|
||||
if key in (curses.KEY_BACKSPACE, 127, '\b'):
|
||||
# Write whitespace over characters to refresh it
|
||||
clear_line(instance, instance.height - 1, len(message) + len(inp) - 1)
|
||||
|
||||
if inp:
|
||||
# Subtract a character from the input list
|
||||
inp.pop()
|
||||
|
||||
else:
|
||||
# Exit the prompt without returning the input
|
||||
return None
|
||||
|
||||
elif key == 27:
|
||||
# Exit the prompt, without returning the input
|
||||
return None
|
||||
|
||||
elif key in (curses.KEY_ENTER, ord('\n'), ord('\r'), ord(":"), ord(";")):
|
||||
# Return the input list
|
||||
return inp
|
||||
|
||||
else:
|
||||
# If any other key is typed, append it
|
||||
# As long as the key is in the valid list
|
||||
valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!/_-0123456789 "
|
||||
if chr(key) in valid and len(inp) < (instance.width - 2):
|
||||
inp.append(chr(key))
|
||||
|
||||
# Refresh the screen
|
||||
instance.refresh()
|
||||
|
||||
# Write the message to the screen
|
||||
instance.screen.addstr(instance.height - 1, 0, message, curses.color_pair(color))
|
||||
|
||||
# Join the input together for visibility on the screen
|
||||
input_text = "".join(inp)
|
||||
|
||||
# Write the input text to the screen
|
||||
instance.screen.addstr(instance.height - 1, len(message), input_text)
|
||||
|
||||
|
||||
def press_key_to_continue(instance, message: str, color: int = 1):
|
||||
# Hide the cursor
|
||||
cursors.mode("hidden")
|
||||
|
||||
# Clear the bottom of the screen
|
||||
clear_line(instance, instance.height - 1, 0)
|
||||
|
||||
# Write the entire message to the screen
|
||||
instance.screen.addstr(instance.height - 1, 0, message, curses.color_pair(color))
|
||||
instance.screen.addstr(instance.height - 1, len(message) + 1, f"(press any key)")
|
||||
|
||||
# Wait for a keypress
|
||||
instance.screen.getch()
|
||||
|
||||
# Clear the bottom of the screen
|
||||
clear_line(instance, instance.height - 1, 0)
|
||||
|
||||
# Show the cursor
|
||||
cursors.mode("visible")
|
||||
|
||||
|
||||
def error(instance, message: str):
|
||||
# Parse the error message
|
||||
error_message = f"ERROR: {message}"
|
||||
|
||||
# Create a prompt
|
||||
press_key_to_continue(instance, error_message, 3)
|
||||
|
||||
|
||||
def fatal_error(exception: Exception):
|
||||
# End the curses session
|
||||
curses.endwin()
|
||||
|
||||
# Print the error message and traceback
|
||||
print(f"{Col.red}FATAL ERROR:{Col.end} "
|
||||
f"{Col.yellow}{exception}{Col.end}\n")
|
||||
print(traceback.format_exc())
|
||||
|
||||
# Exit, with an error exit code
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def goodbye(instance):
|
||||
try:
|
||||
# Confirm before exiting
|
||||
choice = prompt(instance, "Really quit lambda? (y/n): ", 11)
|
||||
|
||||
# If the user confirms, exit
|
||||
if choice and choice[0] == "y":
|
||||
gracefully_exit()
|
||||
|
||||
# Clear the prompt if the user cancels
|
||||
else:
|
||||
clear_line(instance, instance.height - 1, 0)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
# If the user presses Ctrl+C, just exit
|
||||
gracefully_exit()
|
||||
|
||||
except Exception as exception:
|
||||
# If there is an error, print the error message and traceback
|
||||
fatal_error(exception)
|
11
install.sh
11
install.sh
@ -1,11 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copy lambda to ~/.local/share
|
||||
mkdir -p ~/.local/share/lambda
|
||||
cp -rf . ~/.local/share/lambda
|
||||
|
||||
# Copy lambda launcher
|
||||
chmod +x ./lambda
|
||||
rm -rf ~/.local/bin/lambda
|
||||
ln -s ~/.local/share/lambda/lambda ~/.local/bin/lambda
|
||||
chmod +x ~/.local/bin/lambda
|
126
main.py
126
main.py
@ -1,126 +0,0 @@
|
||||
import argparse
|
||||
import curses
|
||||
import os
|
||||
|
||||
from core import colors, cursors, buffers, modes, utils
|
||||
from core.buffers import Buffer
|
||||
from core.components import Components
|
||||
|
||||
|
||||
class Lambda:
|
||||
def __init__(self, buffer: Buffer = None, config: dict = None):
|
||||
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.mode = "normal"
|
||||
self.config = config or {"icon": "λ"}
|
||||
self.buffer = buffer or [""]
|
||||
self.screen = curses.initscr()
|
||||
self.components = Components(self)
|
||||
|
||||
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"]) - 2
|
||||
self.safe_width = self.width - self.components.get_component_width(self.components.components["left"]) - 1
|
||||
|
||||
def refresh(self):
|
||||
# Calculate the cursor position in the file
|
||||
self.cursor[0], self.cursor[1] = self.raw_cursor[0] + self.offset[0], self.raw_cursor[1] + self.offset[1]
|
||||
|
||||
# Update the dimensions of the terminal
|
||||
self.update_dimensions()
|
||||
|
||||
# Write the buffer to the screen
|
||||
self.buffer.render(self)
|
||||
|
||||
# Refresh the on-screen components
|
||||
self.components.render(self)
|
||||
|
||||
# Move the cursor
|
||||
cursors.move(self)
|
||||
|
||||
def start(self):
|
||||
# Change the escape key delay to 25ms
|
||||
# Fixes an issue where the "esc" key takes way too long to press
|
||||
os.environ.setdefault("ESCDELAY", "25")
|
||||
|
||||
# Initialise colors
|
||||
colors.init_colors()
|
||||
|
||||
# Change the cursor shape
|
||||
cursors.mode("block")
|
||||
|
||||
# Don't echo any key-presses
|
||||
curses.noecho()
|
||||
|
||||
# Show a welcome message if lambda opens with no file
|
||||
if not self.buffer.path:
|
||||
# Update the screen variables
|
||||
self.refresh()
|
||||
|
||||
# Show the welcome message
|
||||
utils.welcome(self)
|
||||
|
||||
# Main loop
|
||||
self.run()
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
# The main loop, which runs until the user quits
|
||||
while True:
|
||||
# Update the screen variables
|
||||
self.refresh()
|
||||
|
||||
# Wait for a keypress
|
||||
key = self.screen.getch()
|
||||
|
||||
# Handle the key
|
||||
modes.handle_key(self, key)
|
||||
|
||||
# Refresh and clear the screen
|
||||
self.screen.erase()
|
||||
|
||||
except KeyboardInterrupt: # Ctrl-C
|
||||
# Create a goodbye prompt
|
||||
utils.goodbye(self)
|
||||
|
||||
# Run the main loop again
|
||||
self.run()
|
||||
|
||||
|
||||
def main():
|
||||
# Shell arguments
|
||||
parser = argparse.ArgumentParser(description="Next generation hackable text editor for nerds.")
|
||||
parser.add_argument("file", metavar="file", type=str, nargs="?",
|
||||
help="The name of a file for lambda to open")
|
||||
|
||||
# Collect the arguments passed into lambda at the shell
|
||||
args = parser.parse_args()
|
||||
|
||||
# Load the file into a Buffer object
|
||||
buffer = buffers.load_file(args.file)
|
||||
|
||||
# Load the config
|
||||
config = utils.load_config_file()
|
||||
|
||||
# Load lambda with the buffer object
|
||||
instance = Lambda(buffer, config)
|
||||
|
||||
# Start the screen, this will loop until exit
|
||||
try:
|
||||
instance.start()
|
||||
|
||||
# Excepts *any* errors that occur
|
||||
except Exception as exception:
|
||||
utils.fatal_error(exception)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,41 +0,0 @@
|
||||
from core import utils
|
||||
|
||||
|
||||
def execute(instance, commands: list):
|
||||
# Only if commands are given
|
||||
if commands:
|
||||
# Check each command in the list of commands
|
||||
for command in commands:
|
||||
if command == "d": # Debug
|
||||
# Create the debug prompt
|
||||
utils.press_key_to_continue(instance, f"Cursor: {instance.cursor} Raw: {instance.raw_cursor} "
|
||||
f"Len: {len(instance.buffer.data)}")
|
||||
|
||||
elif command == "t": # Toggle
|
||||
# Toggle the status bar theme
|
||||
if instance.components.components["bottom"][0].theme == "default":
|
||||
instance.components.components["bottom"][0].theme = "inverted"
|
||||
else:
|
||||
instance.components.components["bottom"][0].theme = "default"
|
||||
|
||||
elif command == "w": # Write
|
||||
# Write to the file
|
||||
utils.save_file(instance, instance.buffer.path, instance.buffer.data)
|
||||
|
||||
elif command == "q": # Quit
|
||||
# Create a goodbye prompt
|
||||
utils.goodbye(instance)
|
||||
|
||||
else: # Invalid command
|
||||
utils.error(instance, f"invalid command: '{command}'")
|
||||
|
||||
|
||||
def activate(instance):
|
||||
# Create a prompt, which returns the input (commands)
|
||||
commands = utils.prompt(instance, ":")
|
||||
|
||||
# Execute the commands given
|
||||
execute(instance, commands)
|
||||
|
||||
# Return to normal mode once all commands are executed
|
||||
instance.mode = "normal"
|
@ -1,29 +0,0 @@
|
||||
import curses
|
||||
|
||||
from core import cursors, modes
|
||||
|
||||
|
||||
def execute(instance, key):
|
||||
if key == 27: # Escape
|
||||
# Switch to normal mode
|
||||
modes.activate(instance, "normal")
|
||||
|
||||
elif key in (curses.KEY_BACKSPACE, 127, '\b'): # Backspace
|
||||
if instance.cursor[1] > 0:
|
||||
# Delete the character before the cursor
|
||||
instance.buffer.delete_char(instance)
|
||||
|
||||
# Move the cursor one to the left
|
||||
cursors.push(instance, 3)
|
||||
|
||||
else:
|
||||
# Insert the character
|
||||
instance.buffer.insert_char(instance, chr(key))
|
||||
|
||||
# Move the cursor one to the right
|
||||
cursors.push(instance, 1)
|
||||
|
||||
|
||||
def activate():
|
||||
# Switch the cursor to a line
|
||||
cursors.mode("line")
|
@ -1,45 +0,0 @@
|
||||
import curses
|
||||
|
||||
from core import cursors, modes, utils
|
||||
|
||||
|
||||
def execute(instance, key):
|
||||
if key == curses.BUTTON1_CLICKED:
|
||||
# Move the cursor to the position clicked
|
||||
utils.prompt(instance, str(curses.getmouse()))
|
||||
|
||||
elif key in (ord("j"), curses.KEY_DOWN):
|
||||
# Move the cursor down
|
||||
cursors.push(instance, "down")
|
||||
|
||||
elif key in (ord("k"), curses.KEY_UP):
|
||||
# Move the cursor up
|
||||
cursors.push(instance, "up")
|
||||
|
||||
elif key in (ord("l"), curses.KEY_RIGHT):
|
||||
# Move the cursor right
|
||||
cursors.push(instance, "right")
|
||||
|
||||
elif key in (ord("h"), curses.KEY_LEFT):
|
||||
# Move the cursor left
|
||||
cursors.push(instance, "left")
|
||||
|
||||
elif key == ord("i"):
|
||||
# Activate insert mode
|
||||
modes.activate(instance, "insert")
|
||||
|
||||
elif key == ord("I"):
|
||||
# Move the cursor to the right
|
||||
cursors.push(instance, "right")
|
||||
|
||||
# Then activate insert mode
|
||||
modes.activate(instance, "insert")
|
||||
|
||||
elif key in (ord(":"), ord(";")):
|
||||
# Activate command mode
|
||||
modes.activate(instance, "command")
|
||||
|
||||
|
||||
def activate():
|
||||
# Switch the cursor to a block
|
||||
cursors.mode("block")
|
21
src/main.rs
Normal file
21
src/main.rs
Normal file
@ -0,0 +1,21 @@
|
||||
struct Buffer {
|
||||
data: Vec<String>,
|
||||
name: &str,
|
||||
}
|
||||
|
||||
struct Editor {
|
||||
buffer: Buffer,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let bufffer = Buffer {
|
||||
data: Vec::from([String::from("a test")]),
|
||||
name: "uhh"
|
||||
};
|
||||
|
||||
let editor = Editor {
|
||||
buffer: bufffer,
|
||||
};
|
||||
|
||||
println!("{}", editor.buffer.data[0])
|
||||
}
|
Loading…
Reference in New Issue
Block a user