Compare commits

..

No commits in common. "7ff58d6f760b181b81cac040980d7d27a3eb948a" and "07e489e825186cc45061f395059acbe7f7e7cde8" have entirely different histories.

19 changed files with 290 additions and 201 deletions

20
.replit
View File

@ -1,20 +0,0 @@
run = "cargo run"
hidden = ["target"]
[packager]
language = "rust"
[packager.features]
packageSearch = true
[languages.rust]
pattern = "**/*.rs"
[languages.rust.languageServer]
start = "rust-analyzer"
[nix]
channel = "stable-22_05"
[gitHubImport]
requiredFiles = [".replit", "replit.nix"]

14
Cargo.lock generated
View File

@ -45,6 +45,13 @@ dependencies = [
"winapi",
]
[[package]]
name = "lambda"
version = "0.1.0"
dependencies = [
"crossterm",
]
[[package]]
name = "libc"
version = "0.2.137"
@ -156,13 +163,6 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "velocity"
version = "0.1.0"
dependencies = [
"crossterm",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"

View File

@ -1,11 +1,11 @@
[package]
name = "velocity"
name = "lambda"
version = "0.1.0"
authors = ["Madeline <maddie@spyhoodle.me>"]
edition = "2021"
description = "Next generation hackable text editor for nerds"
homepage = "https://github.com/SpyHoodle/velocity"
repository = "https://github.com/SpyHoodle/velocity"
homepage = "https://github.com/SpyHoodle/lambda"
repository = "https://github.com/SpyHoodle/lambda"
readme = "README.md"
include = ["src/*.rs", "Cargo.toml"]
categories = ["text-editors"]

View File

@ -1,29 +1,29 @@
# λ Velocity
# λ Lambda
A next-generation hackable incredibly performant rust text editor for nerds.
> ⚠️ Velocity is in *very* early stages at the moment. Velocity's goals are still being decided and features may completely change.
> ⚠️ Lambda is in *very* early stages at the moment. Lambda's goals are still being decided and features may completely change.
## Overview
Velocity is a similar text editor to `vim` or `kakoune`, taking some ideas from `xi`.
Lambda is a similar text editor to `vim` or `kakoune`, taking some ideas from `xi`.
The main goal is to build the best text editor possible by taking ideas from existing text editors and implementing them in the best possible way.
- Velocity is written in Rust, so it's incredibly fast and logical
- Lambda is written in Rust, so it's incredibly fast and logical
- It's also full of comments, so anyone can try and learn what it's doing
- Velocity is very modular and extensible, so features can be easily added through a variety of methods
- Lambda is very modular and extensible, so features can be easily added through a variety of methods
- Need to run a set of keybinds in order? Create a macro
- Need to create a completely new feature? Just fork it and write it in Rust
- Velocity is separated between the core and the standard terminal implementation
- Lambda is separated between the core and the standard terminal implementation
- This means that anyone can implement their own keybinds, ui, themes, styling, etc.
- This also means that there is one standard way for managing the text itself - inside of the Velocity core
- Velocity is a modal text editor and uses ideas from kakoune, and is designed for the select -> action structure
- This also means that there is one standard way for managing the text itself - inside of the lambda core
- Lambda is a modal text editor and uses ideas from kakoune, and is designed for the select -> action structure
- Since anyone can implement their own keybinds, it's possible to make a vim implementation that uses the action -> select structure
- Velocity follows the unix philosophy of "do one thing and do it well"
- 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
- Velocity has much better default keybindings than other text editors
- Lambda has much better default keybindings than other text editors
## Getting started
You'll need `cargo` (ideally from `rustup`) and an up to date version of `rust`.
```bash
git clone https://github.com/SpyHoodle/velocity.git # Clone the repositiory
cargo run # Build and run velocity!
git clone https://github.com/SpyHoodle/lambda.git # Clone the repositiory
cargo run # Build and run lambda!
```

View File

@ -0,0 +1,2 @@
- [ ] Modularise functions
- [ ] Make components (i.e statusbar) modular

View File

@ -1,9 +0,0 @@
{ pkgs }: {
deps = [
pkgs.rustc
pkgs.rustfmt
pkgs.cargo
pkgs.cargo-edit
pkgs.rust-analyzer
];
}

View File

@ -1,37 +0,0 @@
use std::path::PathBuf;
#[derive(PartialEq)]
pub enum BufferKind {
Scratch,
Write,
Read,
}
impl BufferKind {
pub fn as_str(&self) -> &str {
match self {
BufferKind::Scratch => "*scratch*",
BufferKind::Write => "write",
BufferKind::Read => "read",
}
}
}
pub struct Buffer<'a> {
pub data: Vec<String>,
pub path: PathBuf,
pub kind: BufferKind,
pub name: &'a str,
}
impl<'a> Buffer<'a> {
pub fn new(path: PathBuf, name: &'a str, kind: BufferKind) -> Self {
// Return a buffer
Self {
data: vec![String::from("")],
path,
kind,
name,
}
}
}

View File

@ -1,7 +1,3 @@
use std::path::PathBuf;
use crate::core::buffer::Buffer;
use crate::core::buffer::BufferKind;
pub struct Config<'a> {
pub logo: &'a str,
pub friendly_name: &'a str,
@ -11,11 +7,17 @@ impl<'a> Config<'a> {
pub fn new() -> Self {
Self {
logo: "λ",
friendly_name: "Velocity",
friendly_name: "Lambda",
}
}
}
pub struct Buffer<'a> {
pub data: Vec<String>,
pub name: &'a str,
pub path: &'a str,
}
#[allow(dead_code)]
pub enum Mode {
Normal,
@ -37,17 +39,20 @@ impl Mode {
pub struct Editor<'a> {
pub config: Config<'a>,
pub buffer: Box<Buffer<'a>>,
pub buffer: Buffer<'a>,
pub cursors: Vec<i32>,
pub mode: Mode,
}
impl<'a> Editor<'a> {
pub fn new(path: PathBuf, buffer_name: &'a str) -> Self {
let buffer_kind = if path.to_str().unwrap().len() > 1 { BufferKind::Write } else { BufferKind::Scratch };
pub fn new() -> Self {
Editor {
config: Config::new(),
buffer: Box::new(Buffer::new(path, buffer_name, buffer_kind)),
buffer: Buffer {
data: Vec::from([String::from("Hello"), String::from("World")]),
name: "[No Name]",
path: "/home/spy",
},
cursors: Vec::from([0]),
mode: Mode::Normal,
}

View File

@ -1,2 +1 @@
pub mod editor;
pub mod buffer;

View File

@ -1,25 +1,9 @@
mod core;
mod terminal;
mod tui;
use std::{env, path::PathBuf};
fn main() {
// Collect command line arguments
let args: Vec<String> = env::args().collect();
// Collect the file path
let file_path = if args.len() > 1 { PathBuf::from(&args[1]) } else { PathBuf::from("") };
// Collect the file name
let file_name = file_path.clone();
let file_name = if args.len() > 1 { file_name.file_name().unwrap().to_str().unwrap() } else { "" };
// Initalise a new editor
let velocity = core::editor::Editor::new(file_path, file_name);
// Initalise a screen
let lambda = core::editor::Editor::new();
let mut screen = terminal::screen::Screen::new().unwrap();
// Begin lambda
tui::ui::start(&mut screen, velocity);
tui::ui::Ui::run(&mut screen, lambda);
}

View File

@ -11,7 +11,7 @@ pub struct Coords {
pub y: usize,
}
// Creating a coordinates from two values
// Creating a set of coordinates from two values
impl Coords {
pub fn from(x: usize, y: usize) -> Self {
Self { x, y }

131
src/terminal/tui.rs Normal file
View File

@ -0,0 +1,131 @@
use crate::core::editor::Editor;
use crate::terminal::screen::{Coords, Screen};
use crossterm::style::Stylize;
use crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers};
// Utils
fn with_spaces(text: &str) -> String {
format!(" {} ", text)
}
fn calc_x(screen_width: usize, item_length: usize) -> usize {
(screen_width / 2) - (item_length / 2)
}
fn longest_element_in_array(elements: Vec<&str>) -> usize {
elements.iter().max_by_key(|x: &&&str| x.len()).unwrap().len()
}
pub fn draw_status(screen: &mut Screen, editor: &Editor) -> Result<(), ()> {
// Calculate where to draw the status bar
let status_height = screen.size.height - 2;
// Get the editor logo from the config
let editor_logo = &with_spaces(editor.config.logo) as &str;
// Get the current mode into a string
let mode_string = &with_spaces(editor.mode.as_str()) as &str;
// Get the current open file name
let file_name = &with_spaces(editor.buffer.name) as &str;
// Calculate the total length of all the status bar components
let total_length = editor_logo.len() + mode_string.len() + file_name.len() + 1;
// If the screen isn't wide enough, panic as we can't draw the status bar
if screen.size.width < total_length {
Err(())
} else {
// Write the editor logo
screen.write_at(
editor_logo.yellow().bold().reverse().to_string(),
Coords::from(0, status_height),
);
// Calculate where to write the current mode
let x = editor_logo.len() - 1;
// Write the current mode
screen.write_at(
mode_string.green().bold().reverse().to_string(),
Coords::from(x, status_height),
);
// Calculate where to write the file name
let x = x + mode_string.len();
// Write the current file name
screen.write_at(
file_name.magenta().bold().reverse().to_string(),
Coords::from(x, status_height),
);
// Draw the rest of the status bar
let x = x + file_name.len();
screen.write_at(
" ".repeat(screen.size.width - x).reverse().to_string(),
Coords::from(x, status_height),
);
Ok(())
}
}
pub fn draw_welcome(screen: &mut Screen, editor: &Editor) {
// The welcome message
let title = format!("{} {}", editor.config.logo, editor.config.friendly_name);
let message: [&str; 5] = [
"Hackable text editor for nerds",
"",
"Type :help to open the README.md document",
"Type :o <file> to open a file and edit",
"Type :q! or <C-c> to quit lambda",
];
// If the screen is big enough, we can draw
if screen.size.width > longest_element_in_array(message.to_vec()) && screen.size.height > message.len() + 4 {
// The starting y position in the centre of the screen
let mut y = (screen.size.height / 2) - (message.len() / 2) - 2;
// Calculate where to place the title
let x = calc_x(screen.size.width, title.len());
// Write the title to the screen
screen.write_at(title.yellow().to_string(), Coords::from(x, y));
for line in message {
// Each line has different width so requires a different x position to center it
let x = calc_x(screen.size.width, line.len()) ;
// For each line we move downwards so increment y
y += 1;
// Write the line to the screen at position (x, y)
screen.write_at(line.to_string(), Coords::from(x, y));
}
}
}
pub fn start(screen: &mut Screen, editor: Editor) {
loop {
// Refresh the screen
screen.refresh().unwrap();
// Draw the welcome message
draw_welcome(screen, &editor);
// Draw the status bar
draw_status(screen, &editor).unwrap();
// Check for any key presses
match read().unwrap() {
Event::Key(KeyEvent {
code: KeyCode::Char('q'),
modifiers: KeyModifiers::CONTROL,
..
}) => break,
Event::Key(KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
..
}) => break,
_ => (),
}
}
screen.exit();
}

View File

@ -1,23 +1,39 @@
use crossterm::style::Stylize;
use crate::tui::ui::Component;
use crate::tui::utils::with_spaces;
use crate::core::editor::Editor;
use crate::terminal::screen::{Coords, Screen};
use crate::tui::utils;
use crossterm::style::Stylize;
pub fn draw(screen: &mut Screen, editor: &Editor) -> Result<(), ()> {
pub struct StatusBar<'a> {
logo: &'a str,
mode: &'a str,
file_name: &'a str,
}
impl<'a> StatusBar<'a> {
pub fn new(editor: &'a Editor<'a>) -> Self {
Self {
logo: editor.config.logo,
mode: editor.mode.as_str(),
file_name: editor.buffer.name,
}
}
}
impl<'a> Component for StatusBar<'a> {
fn draw(&self, screen: &mut Screen, editor: &Editor) -> Result<(), ()> {
// Calculate where to draw the status bar
let status_height = screen.size.height - 2;
// Get the editor logo from the config
let editor_logo = &utils::with_spaces(editor.config.logo) as &str;
let editor_logo = &with_spaces(editor.config.logo) as &str;
// Get the current mode into a string
let mode_string = &utils::with_spaces(editor.mode.as_str()) as &str;
let mode_string = &with_spaces(editor.mode.as_str()) as &str;
// Get the current open file name
let file_name = &utils::with_spaces(editor.buffer.name) as &str;
// Get the current buffer kind
let buffer_kind = &utils::with_spaces(editor.buffer.kind.as_str()) as &str;
let file_name = &with_spaces(editor.buffer.name) as &str;
// Calculate the total length of all the status bar components
let total_length = editor_logo.len() + mode_string.len() + file_name.len() + buffer_kind.len() + 1;
let total_length = editor_logo.len() + mode_string.len() + file_name.len() + 1;
// If the screen isn't wide enough, panic as we can't draw the status bar
if screen.size.width < total_length {
@ -37,22 +53,16 @@ pub fn draw(screen: &mut Screen, editor: &Editor) -> Result<(), ()> {
mode_string.green().bold().reverse().to_string(),
Coords::from(x, status_height),
);
// Calculate where to write the file name
let x = x + mode_string.len();
// Draws the file name if it has a length, if not then it will draw the buffer type
if editor.buffer.name.len() > 0 {
// Write the current file name
screen.write_at(
file_name.magenta().bold().reverse().to_string(),
Coords::from(x, status_height),
);
} else {
screen.write_at(
buffer_kind.blue().bold().reverse().to_string(),
Coords::from(x, status_height),
);
}
let x = if editor.buffer.name.len() > 0 { x + file_name.len() } else { x + buffer_kind.len() };
// Draw the rest of the status bar
let x = x + file_name.len();
screen.write_at(
" ".repeat(screen.size.width - x).reverse().to_string(),
Coords::from(x, status_height),
@ -60,4 +70,5 @@ pub fn draw(screen: &mut Screen, editor: &Editor) -> Result<(), ()> {
Ok(())
}
}
}

View File

@ -1,9 +1,9 @@
use crossterm::style::Stylize;
use crate::tui::utils;
use crate::core::editor::Editor;
use crate::terminal::screen::{Coords, Screen};
use crate::tui::utils;
use crossterm::style::Stylize;
pub fn draw(screen: &mut Screen, editor: &Editor) {
pub fn draw_welcome(screen: &mut Screen, editor: &Editor) {
// The welcome message
let title = format!("{} {}", editor.config.logo, editor.config.friendly_name);
let message: [&str; 5] = [
@ -11,7 +11,7 @@ pub fn draw(screen: &mut Screen, editor: &Editor) {
"",
"Type :help to open the README.md document",
"Type :o <file> to open a file and edit",
"Type :q! or <C-c> to quit the editor",
"Type :q! or <C-c> to quit lambda",
];
// If the screen is big enough, we can draw
@ -20,14 +20,14 @@ pub fn draw(screen: &mut Screen, editor: &Editor) {
let mut y = (screen.size.height / 2) - (message.len() / 2) - 2;
// Calculate where to place the title
let x = utils::calc_centred_x(screen.size.width, title.len());
let x = utils::calc_centered_x(screen.size.width, title.len());
// Write the title to the screen
screen.write_at(title.yellow().to_string(), Coords::from(x, y));
for line in message {
// Each line has different width so requires a different x position to center it
let x = utils::calc_centred_x(screen.size.width, line.len()) ;
let x = utils::calc_centered_x(screen.size.width, line.len()) ;
// For each line we move downwards so increment y
y += 1;
@ -37,3 +37,4 @@ pub fn draw(screen: &mut Screen, editor: &Editor) {
}
}
}

View File

@ -1,3 +1,3 @@
pub mod ui;
pub mod utils;
mod components;
pub mod components;
mod utils;

View File

@ -1,22 +1,43 @@
use crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers};
use crate::core::editor::Editor;
use crate::core::buffer::BufferKind;
use crate::terminal::screen::Screen;
use crate::tui::components;
use crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers};
pub fn start(screen: &mut Screen, editor: Editor) {
// Main screen loop, runs until the program exits
pub trait Component {
fn draw(&self, screen: &mut Screen, editor: &Editor) -> Result<(), ()>;
}
struct Components {
bottom: Vec<Box<dyn Component>>,
}
pub struct Ui {
components: Components,
}
impl Ui {
pub fn new<'a>(editor: &'a Editor<'a>) -> Self {
let status_bar = components::statusbar::StatusBar::new(editor);
Self {
components: Components { bottom: vec![Box::new(status_bar)]},
}
}
pub fn draw(&self) {
}
pub fn run(screen: &mut Screen, editor: Editor) {
loop {
// Refresh the screen
screen.refresh().unwrap();
// Draw the welcome message only if it is a scratch buffer
if editor.buffer.kind == BufferKind::Scratch {
components::welcome::draw(screen, &editor);
};
// Generate all the UI elements
let components = Ui::new(&editor);
// Draw the status bar
components::statusbar::draw(screen, &editor).unwrap();
// Draw all UI elements
components.draw();
// Check for any key presses
match read().unwrap() {
@ -35,4 +56,5 @@ pub fn start(screen: &mut Screen, editor: Editor) {
}
screen.exit();
}
}

View File

@ -3,12 +3,12 @@ pub fn with_spaces(text: &str) -> String {
format!(" {} ", text)
}
// Calculates the starting x coordinate for centred text
pub fn calc_centred_x(screen_width: usize, item_length: usize) -> usize {
// Calculates the x coordinate for centered text
pub fn calc_centered_x(screen_width: usize, item_length: usize) -> usize {
(screen_width / 2) - (item_length / 2)
}
// Returns the longest element in a vector
// Finds and returns the longest element in a vector
pub fn longest_element_in_vec(elements: Vec<&str>) -> usize {
elements.iter().max_by_key(|x: &&&str| x.len()).unwrap().len()
}