Compare commits
10 Commits
07e489e825
...
7ff58d6f76
Author | SHA1 | Date | |
---|---|---|---|
7ff58d6f76 | |||
|
6c12ccb4c1 | ||
|
08081d54e2 | ||
cb005ef441 | |||
|
5b49d25d9e | ||
|
6f792ff546 | ||
|
25b0da3962 | ||
|
1d18cd1715 | ||
a8c7733c0a | |||
e050cb568c |
20
.replit
Normal file
20
.replit
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
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
14
Cargo.lock
generated
@ -45,13 +45,6 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lambda"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"crossterm",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.137"
|
version = "0.2.137"
|
||||||
@ -163,6 +156,13 @@ version = "1.10.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "velocity"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"crossterm",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lambda"
|
name = "velocity"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Madeline <maddie@spyhoodle.me>"]
|
authors = ["Madeline <maddie@spyhoodle.me>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Next generation hackable text editor for nerds"
|
description = "Next generation hackable text editor for nerds"
|
||||||
homepage = "https://github.com/SpyHoodle/lambda"
|
homepage = "https://github.com/SpyHoodle/velocity"
|
||||||
repository = "https://github.com/SpyHoodle/lambda"
|
repository = "https://github.com/SpyHoodle/velocity"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
include = ["src/*.rs", "Cargo.toml"]
|
include = ["src/*.rs", "Cargo.toml"]
|
||||||
categories = ["text-editors"]
|
categories = ["text-editors"]
|
||||||
|
24
README.md
24
README.md
@ -1,29 +1,29 @@
|
|||||||
# λ Lambda
|
# λ Velocity
|
||||||
A next-generation hackable incredibly performant rust text editor for nerds.
|
A next-generation hackable incredibly performant rust text editor for nerds.
|
||||||
> ⚠️ Lambda is in *very* early stages at the moment. Lambda's goals are still being decided and features may completely change.
|
> ⚠️ Velocity is in *very* early stages at the moment. Velocity's goals are still being decided and features may completely change.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
Lambda is a similar text editor to `vim` or `kakoune`, taking some ideas from `xi`.
|
Velocity 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.
|
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.
|
||||||
|
|
||||||
- Lambda is written in Rust, so it's incredibly fast and logical
|
- Velocity 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
|
- It's also full of comments, so anyone can try and learn what it's doing
|
||||||
- Lambda is very modular and extensible, so features can be easily added through a variety of methods
|
- Velocity 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 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
|
- Need to create a completely new feature? Just fork it and write it in Rust
|
||||||
- Lambda is separated between the core and the standard terminal implementation
|
- Velocity is separated between the core and the standard terminal implementation
|
||||||
- This means that anyone can implement their own keybinds, ui, themes, styling, etc.
|
- 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 lambda core
|
- This also means that there is one standard way for managing the text itself - inside of the Velocity core
|
||||||
- Lambda is a modal text editor and uses ideas from kakoune, and is designed for the select -> action structure
|
- Velocity 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
|
- Since anyone can implement their own keybinds, it's possible to make a vim implementation that uses the action -> select structure
|
||||||
- Lambda follows the unix philosophy of "do one thing and do it well"
|
- Velocity follows the unix philosophy of "do one thing and do it well"
|
||||||
- It has no bloated features like splits or tabs
|
- It has no bloated features like splits or tabs
|
||||||
- It contains the bare necessities and provides a few extra modules
|
- It contains the bare necessities and provides a few extra modules
|
||||||
- Lambda has much better default keybindings than other text editors
|
- Velocity has much better default keybindings than other text editors
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
You'll need `cargo` (ideally from `rustup`) and an up to date version of `rust`.
|
You'll need `cargo` (ideally from `rustup`) and an up to date version of `rust`.
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/SpyHoodle/lambda.git # Clone the repositiory
|
git clone https://github.com/SpyHoodle/velocity.git # Clone the repositiory
|
||||||
cargo run # Build and run lambda!
|
cargo run # Build and run velocity!
|
||||||
```
|
```
|
||||||
|
2
TODO.md
2
TODO.md
@ -1,2 +0,0 @@
|
|||||||
- [ ] Modularise functions
|
|
||||||
- [ ] Make components (i.e statusbar) modular
|
|
9
replit.nix
Normal file
9
replit.nix
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{ pkgs }: {
|
||||||
|
deps = [
|
||||||
|
pkgs.rustc
|
||||||
|
pkgs.rustfmt
|
||||||
|
pkgs.cargo
|
||||||
|
pkgs.cargo-edit
|
||||||
|
pkgs.rust-analyzer
|
||||||
|
];
|
||||||
|
}
|
37
src/core/buffer.rs
Normal file
37
src/core/buffer.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,7 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
use crate::core::buffer::Buffer;
|
||||||
|
use crate::core::buffer::BufferKind;
|
||||||
|
|
||||||
pub struct Config<'a> {
|
pub struct Config<'a> {
|
||||||
pub logo: &'a str,
|
pub logo: &'a str,
|
||||||
pub friendly_name: &'a str,
|
pub friendly_name: &'a str,
|
||||||
@ -7,17 +11,11 @@ impl<'a> Config<'a> {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
logo: "λ",
|
logo: "λ",
|
||||||
friendly_name: "Lambda",
|
friendly_name: "Velocity",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Buffer<'a> {
|
|
||||||
pub data: Vec<String>,
|
|
||||||
pub name: &'a str,
|
|
||||||
pub path: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
Normal,
|
Normal,
|
||||||
@ -39,20 +37,17 @@ impl Mode {
|
|||||||
|
|
||||||
pub struct Editor<'a> {
|
pub struct Editor<'a> {
|
||||||
pub config: Config<'a>,
|
pub config: Config<'a>,
|
||||||
pub buffer: Buffer<'a>,
|
pub buffer: Box<Buffer<'a>>,
|
||||||
pub cursors: Vec<i32>,
|
pub cursors: Vec<i32>,
|
||||||
pub mode: Mode,
|
pub mode: Mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Editor<'a> {
|
impl<'a> Editor<'a> {
|
||||||
pub fn new() -> Self {
|
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 };
|
||||||
Editor {
|
Editor {
|
||||||
config: Config::new(),
|
config: Config::new(),
|
||||||
buffer: Buffer {
|
buffer: Box::new(Buffer::new(path, buffer_name, buffer_kind)),
|
||||||
data: Vec::from([String::from("Hello"), String::from("World")]),
|
|
||||||
name: "[No Name]",
|
|
||||||
path: "/home/spy",
|
|
||||||
},
|
|
||||||
cursors: Vec::from([0]),
|
cursors: Vec::from([0]),
|
||||||
mode: Mode::Normal,
|
mode: Mode::Normal,
|
||||||
}
|
}
|
||||||
|
@ -1 +1,2 @@
|
|||||||
pub mod editor;
|
pub mod editor;
|
||||||
|
pub mod buffer;
|
||||||
|
20
src/main.rs
20
src/main.rs
@ -1,9 +1,25 @@
|
|||||||
mod core;
|
mod core;
|
||||||
mod terminal;
|
mod terminal;
|
||||||
mod tui;
|
mod tui;
|
||||||
|
use std::{env, path::PathBuf};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let lambda = core::editor::Editor::new();
|
// 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 mut screen = terminal::screen::Screen::new().unwrap();
|
let mut screen = terminal::screen::Screen::new().unwrap();
|
||||||
tui::ui::Ui::run(&mut screen, lambda);
|
|
||||||
|
// Begin lambda
|
||||||
|
tui::ui::start(&mut screen, velocity);
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
pub mod screen;
|
pub mod screen;
|
@ -11,7 +11,7 @@ pub struct Coords {
|
|||||||
pub y: usize,
|
pub y: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creating a set of coordinates from two values
|
// Creating a coordinates from two values
|
||||||
impl Coords {
|
impl Coords {
|
||||||
pub fn from(x: usize, y: usize) -> Self {
|
pub fn from(x: usize, y: usize) -> Self {
|
||||||
Self { x, y }
|
Self { x, y }
|
||||||
|
@ -1,131 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
@ -1,2 +1,2 @@
|
|||||||
pub mod statusbar;
|
pub mod statusbar;
|
||||||
pub mod welcome;
|
pub mod welcome;
|
@ -1,74 +1,63 @@
|
|||||||
use crate::tui::ui::Component;
|
use crossterm::style::Stylize;
|
||||||
use crate::tui::utils::with_spaces;
|
|
||||||
use crate::core::editor::Editor;
|
use crate::core::editor::Editor;
|
||||||
use crate::terminal::screen::{Coords, Screen};
|
use crate::terminal::screen::{Coords, Screen};
|
||||||
use crossterm::style::Stylize;
|
use crate::tui::utils;
|
||||||
|
|
||||||
pub struct StatusBar<'a> {
|
pub fn draw(screen: &mut Screen, editor: &Editor) -> Result<(), ()> {
|
||||||
logo: &'a str,
|
// Calculate where to draw the status bar
|
||||||
mode: &'a str,
|
let status_height = screen.size.height - 2;
|
||||||
file_name: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> StatusBar<'a> {
|
// Get the editor logo from the config
|
||||||
pub fn new(editor: &'a Editor<'a>) -> Self {
|
let editor_logo = &utils::with_spaces(editor.config.logo) as &str;
|
||||||
Self {
|
// Get the current mode into a string
|
||||||
logo: editor.config.logo,
|
let mode_string = &utils::with_spaces(editor.mode.as_str()) as &str;
|
||||||
mode: editor.mode.as_str(),
|
// Get the current open file name
|
||||||
file_name: editor.buffer.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;
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Component for StatusBar<'a> {
|
// Calculate the total length of all the status bar components
|
||||||
fn draw(&self, screen: &mut Screen, editor: &Editor) -> Result<(), ()> {
|
let total_length = editor_logo.len() + mode_string.len() + file_name.len() + buffer_kind.len() + 1;
|
||||||
// Calculate where to draw the status bar
|
|
||||||
let status_height = screen.size.height - 2;
|
|
||||||
|
|
||||||
// Get the editor logo from the config
|
// If the screen isn't wide enough, panic as we can't draw the status bar
|
||||||
let editor_logo = &with_spaces(editor.config.logo) as &str;
|
if screen.size.width < total_length {
|
||||||
// Get the current mode into a string
|
Err(())
|
||||||
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
|
} else {
|
||||||
let total_length = editor_logo.len() + mode_string.len() + file_name.len() + 1;
|
// Write the editor logo
|
||||||
|
screen.write_at(
|
||||||
|
editor_logo.yellow().bold().reverse().to_string(),
|
||||||
|
Coords::from(0, status_height),
|
||||||
|
);
|
||||||
|
|
||||||
// If the screen isn't wide enough, panic as we can't draw the status bar
|
// Calculate where to write the current mode
|
||||||
if screen.size.width < total_length {
|
let x = editor_logo.len() - 1;
|
||||||
Err(())
|
// Write the current mode
|
||||||
|
screen.write_at(
|
||||||
|
mode_string.green().bold().reverse().to_string(),
|
||||||
|
Coords::from(x, status_height),
|
||||||
|
);
|
||||||
|
|
||||||
} else {
|
let x = x + mode_string.len();
|
||||||
// Write the editor logo
|
// Draws the file name if it has a length, if not then it will draw the buffer type
|
||||||
screen.write_at(
|
if editor.buffer.name.len() > 0 {
|
||||||
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(
|
screen.write_at(
|
||||||
file_name.magenta().bold().reverse().to_string(),
|
file_name.magenta().bold().reverse().to_string(),
|
||||||
Coords::from(x, status_height),
|
Coords::from(x, status_height),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
// Draw the rest of the status bar
|
|
||||||
let x = x + file_name.len();
|
|
||||||
screen.write_at(
|
screen.write_at(
|
||||||
" ".repeat(screen.size.width - x).reverse().to_string(),
|
buffer_kind.blue().bold().reverse().to_string(),
|
||||||
Coords::from(x, status_height),
|
Coords::from(x, status_height),
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let x = if editor.buffer.name.len() > 0 { x + file_name.len() } else { x + buffer_kind.len() };
|
||||||
|
screen.write_at(
|
||||||
|
" ".repeat(screen.size.width - x).reverse().to_string(),
|
||||||
|
Coords::from(x, status_height),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use crate::tui::utils;
|
use crossterm::style::Stylize;
|
||||||
use crate::core::editor::Editor;
|
use crate::core::editor::Editor;
|
||||||
use crate::terminal::screen::{Coords, Screen};
|
use crate::terminal::screen::{Coords, Screen};
|
||||||
use crossterm::style::Stylize;
|
use crate::tui::utils;
|
||||||
|
|
||||||
pub fn draw_welcome(screen: &mut Screen, editor: &Editor) {
|
pub fn draw(screen: &mut Screen, editor: &Editor) {
|
||||||
// The welcome message
|
// The welcome message
|
||||||
let title = format!("{} {}", editor.config.logo, editor.config.friendly_name);
|
let title = format!("{} {}", editor.config.logo, editor.config.friendly_name);
|
||||||
let message: [&str; 5] = [
|
let message: [&str; 5] = [
|
||||||
@ -11,7 +11,7 @@ pub fn draw_welcome(screen: &mut Screen, editor: &Editor) {
|
|||||||
"",
|
"",
|
||||||
"Type :help to open the README.md document",
|
"Type :help to open the README.md document",
|
||||||
"Type :o <file> to open a file and edit",
|
"Type :o <file> to open a file and edit",
|
||||||
"Type :q! or <C-c> to quit lambda",
|
"Type :q! or <C-c> to quit the editor",
|
||||||
];
|
];
|
||||||
|
|
||||||
// If the screen is big enough, we can draw
|
// If the screen is big enough, we can draw
|
||||||
@ -20,14 +20,14 @@ pub fn draw_welcome(screen: &mut Screen, editor: &Editor) {
|
|||||||
let mut y = (screen.size.height / 2) - (message.len() / 2) - 2;
|
let mut y = (screen.size.height / 2) - (message.len() / 2) - 2;
|
||||||
|
|
||||||
// Calculate where to place the title
|
// Calculate where to place the title
|
||||||
let x = utils::calc_centered_x(screen.size.width, title.len());
|
let x = utils::calc_centred_x(screen.size.width, title.len());
|
||||||
|
|
||||||
// Write the title to the screen
|
// Write the title to the screen
|
||||||
screen.write_at(title.yellow().to_string(), Coords::from(x, y));
|
screen.write_at(title.yellow().to_string(), Coords::from(x, y));
|
||||||
|
|
||||||
for line in message {
|
for line in message {
|
||||||
// Each line has different width so requires a different x position to center it
|
// Each line has different width so requires a different x position to center it
|
||||||
let x = utils::calc_centered_x(screen.size.width, line.len()) ;
|
let x = utils::calc_centred_x(screen.size.width, line.len()) ;
|
||||||
|
|
||||||
// For each line we move downwards so increment y
|
// For each line we move downwards so increment y
|
||||||
y += 1;
|
y += 1;
|
||||||
@ -37,4 +37,3 @@ pub fn draw_welcome(screen: &mut Screen, editor: &Editor) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
pub mod ui;
|
pub mod ui;
|
||||||
pub mod components;
|
pub mod utils;
|
||||||
mod utils;
|
mod components;
|
@ -1,60 +1,38 @@
|
|||||||
use crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers};
|
|
||||||
use crate::core::editor::Editor;
|
use crate::core::editor::Editor;
|
||||||
|
use crate::core::buffer::BufferKind;
|
||||||
use crate::terminal::screen::Screen;
|
use crate::terminal::screen::Screen;
|
||||||
use crate::tui::components;
|
use crate::tui::components;
|
||||||
|
use crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers};
|
||||||
|
|
||||||
pub trait Component {
|
pub fn start(screen: &mut Screen, editor: Editor) {
|
||||||
fn draw(&self, screen: &mut Screen, editor: &Editor) -> Result<(), ()>;
|
// Main screen loop, runs until the program exits
|
||||||
}
|
loop {
|
||||||
|
// Refresh the screen
|
||||||
|
screen.refresh().unwrap();
|
||||||
|
|
||||||
struct Components {
|
// Draw the welcome message only if it is a scratch buffer
|
||||||
bottom: Vec<Box<dyn Component>>,
|
if editor.buffer.kind == BufferKind::Scratch {
|
||||||
}
|
components::welcome::draw(screen, &editor);
|
||||||
|
};
|
||||||
|
|
||||||
pub struct Ui {
|
// Draw the status bar
|
||||||
components: Components,
|
components::statusbar::draw(screen, &editor).unwrap();
|
||||||
}
|
|
||||||
|
|
||||||
impl Ui {
|
// Check for any key presses
|
||||||
pub fn new<'a>(editor: &'a Editor<'a>) -> Self {
|
match read().unwrap() {
|
||||||
let status_bar = components::statusbar::StatusBar::new(editor);
|
Event::Key(KeyEvent {
|
||||||
|
code: KeyCode::Char('q'),
|
||||||
Self {
|
modifiers: KeyModifiers::CONTROL,
|
||||||
components: Components { bottom: vec![Box::new(status_bar)]},
|
..
|
||||||
|
}) => break,
|
||||||
|
Event::Key(KeyEvent {
|
||||||
|
code: KeyCode::Char('c'),
|
||||||
|
modifiers: KeyModifiers::CONTROL,
|
||||||
|
..
|
||||||
|
}) => break,
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&self) {
|
screen.exit();
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(screen: &mut Screen, editor: Editor) {
|
|
||||||
loop {
|
|
||||||
// Refresh the screen
|
|
||||||
screen.refresh().unwrap();
|
|
||||||
|
|
||||||
// Generate all the UI elements
|
|
||||||
let components = Ui::new(&editor);
|
|
||||||
|
|
||||||
// Draw all UI elements
|
|
||||||
components.draw();
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,12 @@ pub fn with_spaces(text: &str) -> String {
|
|||||||
format!(" {} ", text)
|
format!(" {} ", text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculates the x coordinate for centered text
|
// Calculates the starting x coordinate for centred text
|
||||||
pub fn calc_centered_x(screen_width: usize, item_length: usize) -> usize {
|
pub fn calc_centred_x(screen_width: usize, item_length: usize) -> usize {
|
||||||
(screen_width / 2) - (item_length / 2)
|
(screen_width / 2) - (item_length / 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finds and returns the longest element in a vector
|
// Returns the longest element in a vector
|
||||||
pub fn longest_element_in_vec(elements: Vec<&str>) -> usize {
|
pub fn longest_element_in_vec(elements: Vec<&str>) -> usize {
|
||||||
elements.iter().max_by_key(|x: &&&str| x.len()).unwrap().len()
|
elements.iter().max_by_key(|x: &&&str| x.len()).unwrap().len()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user