red: move more logic into lysp
This commit is contained in:
@@ -1,5 +1,16 @@
|
|||||||
(setq _red/command-table (hash/new))
|
(setq _red/command-table (hash/new))
|
||||||
|
|
||||||
|
;; Command text manipulation
|
||||||
|
(setq _red/current-command nil)
|
||||||
|
|
||||||
|
(defun red/command/append! (text)
|
||||||
|
(setq _red/current-command (+ (if _red/current-command _red/current-command "") text)))
|
||||||
|
(defun red/command/erase-backward! ()
|
||||||
|
(when _red/current-command
|
||||||
|
(setq _red/current-command (string/pop _red/current-command))))
|
||||||
|
(defun red/command/clear! () (setq _red/current-command nil))
|
||||||
|
|
||||||
|
;; Command macros
|
||||||
(defun _red/declare-command
|
(defun _red/declare-command
|
||||||
(command handler)
|
(command handler)
|
||||||
(hash/put! _red/command-table command handler))
|
(hash/put! _red/command-table command handler))
|
||||||
@@ -21,6 +32,7 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
;; Command handlers
|
||||||
(defun _red/editor-command-hook (command)
|
(defun _red/editor-command-hook (command)
|
||||||
(when-let
|
(when-let
|
||||||
(words (filter identity (string/split command)))
|
(words (filter identity (string/split command)))
|
||||||
@@ -37,6 +49,7 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
;; Root command handler
|
||||||
(defun _red/root-command-hook (command)
|
(defun _red/root-command-hook (command)
|
||||||
(red/set-top-mode 'normal)
|
(red/set-top-mode 'normal)
|
||||||
(setq command (string/trim command))
|
(setq command (string/trim command))
|
||||||
@@ -49,6 +62,7 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
;; Command definitions
|
||||||
(declare-command "q" () (red/quit))
|
(declare-command "q" () (red/quit))
|
||||||
(declare-command "q!" () (red/quit #t))
|
(declare-command "q!" () (red/quit #t))
|
||||||
(declare-command
|
(declare-command
|
||||||
|
|||||||
@@ -1,23 +1,10 @@
|
|||||||
;; External API
|
;; External API
|
||||||
|
|
||||||
(import "util.lysp")
|
(import "util.lysp")
|
||||||
|
(import "message.lysp")
|
||||||
(defun _red/key-sequence-string
|
|
||||||
(key-seq)
|
|
||||||
(let (strs (map ->string key-seq))
|
|
||||||
(string/join strs "+")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(defun _red/root-post-render-hook (width height)
|
|
||||||
(unless (nil? _red/key-sequence)
|
|
||||||
(let (keys (_red/key-sequence-string _red/key-sequence))
|
|
||||||
(term/set-cursor (- height 1) (- width 10))
|
|
||||||
(term/write keys)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
;; Bind the hooks
|
;; Bind the hooks
|
||||||
|
(import "render.lysp")
|
||||||
(red/bind-post-render-hook _red/root-post-render-hook)
|
(red/bind-post-render-hook _red/root-post-render-hook)
|
||||||
|
|
||||||
;; Child modules
|
;; Child modules
|
||||||
@@ -30,3 +17,5 @@
|
|||||||
;; User configuration
|
;; User configuration
|
||||||
(try-import "/etc/red/init.lysp")
|
(try-import "/etc/red/init.lysp")
|
||||||
(try-import (+ (fs/home-directory) "/.red.d/init.lysp"))
|
(try-import (+ (fs/home-directory) "/.red.d/init.lysp"))
|
||||||
|
|
||||||
|
(_red/update-render-params)
|
||||||
|
|||||||
@@ -2,16 +2,13 @@
|
|||||||
command
|
command
|
||||||
('escape (red/set-top-mode 'normal))
|
('escape (red/set-top-mode 'normal))
|
||||||
('backspace
|
('backspace
|
||||||
(let (command (red/command))
|
(if _red/current-command
|
||||||
(if command
|
(red/command/erase-backward!)
|
||||||
(red/set-command (string/pop command))
|
(red/set-top-mode 'normal)
|
||||||
(red/set-top-mode 'normal)
|
))
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
('newline
|
('newline
|
||||||
(let (command (red/command))
|
(let (command _red/current-command)
|
||||||
(red/set-command "")
|
(red/command/clear!)
|
||||||
(_red/root-command-hook command)
|
(_red/root-command-hook command)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -24,8 +21,4 @@
|
|||||||
(key-seq)
|
(key-seq)
|
||||||
(let (insertable (red/as-insertable-key-seq key-seq))
|
(let (insertable (red/as-insertable-key-seq key-seq))
|
||||||
(unless (nil? insertable)
|
(unless (nil? insertable)
|
||||||
(let (command (+ (red/command) insertable))
|
(red/command/append! insertable))))
|
||||||
(red/set-command command))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
(declare-keys
|
(declare-keys
|
||||||
normal
|
normal
|
||||||
(': (red/set-top-mode 'command))
|
(':
|
||||||
|
(red/clear-status)
|
||||||
|
(red/clear-message)
|
||||||
|
(red/set-top-mode 'command)
|
||||||
|
)
|
||||||
('i (red/buffer/set-mode 'insert-before))
|
('i (red/buffer/set-mode 'insert-before))
|
||||||
('a (red/buffer/set-mode 'insert-after))
|
('a (red/buffer/set-mode 'insert-after))
|
||||||
('I
|
('I
|
||||||
@@ -28,6 +32,8 @@
|
|||||||
('up (red/buffer/move 'prev-line))
|
('up (red/buffer/move 'prev-line))
|
||||||
('down (red/buffer/move 'next-line))
|
('down (red/buffer/move 'next-line))
|
||||||
|
|
||||||
|
('(F F F F F) nil)
|
||||||
|
|
||||||
('o
|
('o
|
||||||
(red/buffer/insert-line-after)
|
(red/buffer/insert-line-after)
|
||||||
(red/buffer/move 'next-line)
|
(red/buffer/move 'next-line)
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
(setq _red/current-message nil)
|
||||||
|
(setq _red/current-status nil)
|
||||||
|
|
||||||
|
(defun _red/set-message (var args)
|
||||||
|
(let
|
||||||
|
(value (if (nil? args)
|
||||||
|
nil
|
||||||
|
(string/join (map ->string args))))
|
||||||
|
(set var value)))
|
||||||
|
|
||||||
|
(defun red/message (&rest args) (_red/set-message '_red/current-message args))
|
||||||
|
(defun red/status (&rest args) (_red/set-message '_red/current-status args))
|
||||||
|
|
||||||
|
(defun red/clear-message () (setq _red/current-message nil))
|
||||||
|
(defun red/clear-status () (setq _red/current-status nil))
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
;; Drawing options
|
||||||
|
|
||||||
|
(setq red/render/status-line #T)
|
||||||
|
(setq red/render/status-line/key-sequence #T)
|
||||||
|
|
||||||
|
(setq red/render/mode-line #T)
|
||||||
|
(setq red/render/mode-line/arrow #T)
|
||||||
|
|
||||||
|
;; Drawing functions
|
||||||
|
|
||||||
|
(defun _red/colorize-mode
|
||||||
|
(mode)
|
||||||
|
(cond
|
||||||
|
((= mode 'normal) '(black cyan))
|
||||||
|
((= mode 'insert) '(black yellow))
|
||||||
|
((= mode 'command) '(black green))
|
||||||
|
(&otherwise nil)))
|
||||||
|
|
||||||
|
(defun _red/render-mode-line
|
||||||
|
(row mode name modified)
|
||||||
|
(unless red/render/mode-line (return))
|
||||||
|
(term/set-cursor row 0)
|
||||||
|
(let (color (_red/colorize-mode mode))
|
||||||
|
(when color
|
||||||
|
(when (car color)
|
||||||
|
(term/fg-color (car color)))
|
||||||
|
(when (cadr color)
|
||||||
|
(term/bg-color (cadr color))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(term/write (+ " " (string/to-upper (->string mode)) " "))
|
||||||
|
;; Invert colors and draw the arrow
|
||||||
|
(when color
|
||||||
|
(when (and red/render/mode-line/arrow (cadr color))
|
||||||
|
(term/fg-color (cadr color))
|
||||||
|
(term/bg-color 'black)
|
||||||
|
(term/write "🭬"))
|
||||||
|
(term/reset)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
;; Render bufer name
|
||||||
|
(when name
|
||||||
|
(term/write (+ " " name)))
|
||||||
|
(when modified
|
||||||
|
(term/write " [+]"))
|
||||||
|
)
|
||||||
|
|
||||||
|
(defun _red/render-status-line
|
||||||
|
(row)
|
||||||
|
(let (message (cond
|
||||||
|
(_red/current-message (list _red/current-message 'red))
|
||||||
|
(_red/current-status (list _red/current-status 'white)))
|
||||||
|
)
|
||||||
|
(unless message (return))
|
||||||
|
(term/set-cursor row 0)
|
||||||
|
(when (cadr message)
|
||||||
|
(term/fg-color (cadr message)))
|
||||||
|
(term/write (car message))))
|
||||||
|
|
||||||
|
(defun _red/render-key-line
|
||||||
|
(row width)
|
||||||
|
(let (keys (string/join (map ->string _red/key-sequence) "+"))
|
||||||
|
(unless keys (return))
|
||||||
|
(term/set-cursor row (- width (+ (string/length keys) 1)))
|
||||||
|
(term/write keys)
|
||||||
|
))
|
||||||
|
|
||||||
|
(defun _red/render-command (row command)
|
||||||
|
(term/set-cursor row 0)
|
||||||
|
(term/write ":")
|
||||||
|
(when command
|
||||||
|
(term/write command)))
|
||||||
|
|
||||||
|
(defun _red/render-bottom-bar
|
||||||
|
(width height)
|
||||||
|
(let* (mode (red/buffer/mode) top-mode (car mode) buffer-mode (cadr mode))
|
||||||
|
(when (= top-mode 'command) (setq buffer-mode 'command))
|
||||||
|
|
||||||
|
;; Display mode line
|
||||||
|
(_red/render-mode-line
|
||||||
|
(- height _red/mode-line-offset)
|
||||||
|
buffer-mode
|
||||||
|
(red/buffer/name)
|
||||||
|
(red/buffer/modified?))
|
||||||
|
|
||||||
|
(when (= top-mode 'normal)
|
||||||
|
;; Status text
|
||||||
|
(_red/render-status-line (- height _red/status-line-offset))
|
||||||
|
;; Current key sequence
|
||||||
|
(_red/render-key-line (- height _red/status-line-offset) width)
|
||||||
|
)
|
||||||
|
(when (= top-mode 'command)
|
||||||
|
;; Render command
|
||||||
|
(_red/render-command (- height _red/status-line-offset) _red/current-command))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(defun _red/root-post-render-hook (width height)
|
||||||
|
(_red/render-bottom-bar width height)
|
||||||
|
(term/reset)
|
||||||
|
;; If not in command mode, set cursor to buffer
|
||||||
|
(when (= 'normal (car (red/buffer/mode)))
|
||||||
|
(red/cursor-to-buffer))
|
||||||
|
)
|
||||||
|
|
||||||
|
;; command line + status line
|
||||||
|
(defun _red/update-render-params ()
|
||||||
|
(setq _red/status-line-offset 1)
|
||||||
|
(setq _red/mode-line-offset 2)
|
||||||
|
(setq red/bottom-margin (+ (and red/render/mode-line 1) 1)))
|
||||||
|
|
||||||
|
(_red/update-render-params)
|
||||||
@@ -6,6 +6,7 @@ use std::{
|
|||||||
pub struct EditorConfig {
|
pub struct EditorConfig {
|
||||||
pub tab_width: usize,
|
pub tab_width: usize,
|
||||||
pub number: Dirty<bool>,
|
pub number: Dirty<bool>,
|
||||||
|
pub bottom_margin: Dirty<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Dirty<T>(T, bool);
|
pub struct Dirty<T>(T, bool);
|
||||||
@@ -15,6 +16,7 @@ impl Default for EditorConfig {
|
|||||||
Self {
|
Self {
|
||||||
tab_width: 4,
|
tab_width: 4,
|
||||||
number: Dirty::new(false),
|
number: Dirty::new(false),
|
||||||
|
bottom_margin: Dirty::new(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ use lysp::{
|
|||||||
Value,
|
Value,
|
||||||
env::{Environment, EnvironmentAccessHook},
|
env::{Environment, EnvironmentAccessHook},
|
||||||
machine::Machine,
|
machine::Machine,
|
||||||
value::{IdentifierValue, StringValue, convert::AnyFunction},
|
value::{
|
||||||
|
IdentifierValue, StringValue,
|
||||||
|
convert::{AnyFunction, TryFromValue},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -57,17 +60,28 @@ impl Editor {
|
|||||||
|
|
||||||
match name {
|
match name {
|
||||||
"red/number" => Some((*config.number).into()),
|
"red/number" => Some((*config.number).into()),
|
||||||
|
"red/bottom-margin" => Some((*config.bottom_margin).into()),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn write_variable(&mut self, name: IdentifierValue, value: Value) -> bool {
|
fn write_variable(
|
||||||
|
&mut self,
|
||||||
|
name: &IdentifierValue,
|
||||||
|
value: &Value,
|
||||||
|
) -> Result<bool, MachineError> {
|
||||||
match name.as_ref() {
|
match name.as_ref() {
|
||||||
"red/number" => {
|
"red/number" => {
|
||||||
let mut config = self.1.borrow_mut();
|
let mut config = self.1.borrow_mut();
|
||||||
*config.number = value.is_trueish();
|
*config.number = value.is_trueish();
|
||||||
true
|
Ok(true)
|
||||||
}
|
}
|
||||||
_ => false,
|
"red/bottom-margin" => {
|
||||||
|
let value = usize::try_from_value(value)?;
|
||||||
|
let mut config = self.1.borrow_mut();
|
||||||
|
*config.bottom_margin = value;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
_ => Ok(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,18 +141,26 @@ impl Editor {
|
|||||||
pub fn evaluate_file<P: AsRef<Path>>(&mut self, path: P) {
|
pub fn evaluate_file<P: AsRef<Path>>(&mut self, path: P) {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
if let Err(error) = self.machine.load_file(Default::default(), &self.env, path) {
|
if let Err(error) = self.machine.load_file(Default::default(), &self.env, path) {
|
||||||
self.state
|
log::error!("{}: {}", path.display(), error);
|
||||||
.borrow_mut()
|
self.env
|
||||||
.set_message(format!("{}: {}", path.display(), error));
|
.set_global_value(
|
||||||
|
"_red/current-message",
|
||||||
|
Value::String(format!("{}: {}", path.display(), error).into()),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn evaluate_callback(&mut self, function: &AnyFunction, args: &[Value]) {
|
pub fn evaluate_callback(&mut self, function: &AnyFunction, args: &[Value]) {
|
||||||
let name = function.name();
|
let name = function.name();
|
||||||
if let Err(error) = function.invoke(&mut self.machine, &self.env, args) {
|
if let Err(error) = function.invoke(&mut self.machine, &self.env, args) {
|
||||||
self.state
|
log::error!("{name}: {error}");
|
||||||
.borrow_mut()
|
self.env
|
||||||
.set_message(format!("{name}: {error}"));
|
.set_global_value(
|
||||||
|
"_red/current-message",
|
||||||
|
Value::String(format!("{name}: {error}").into()),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,10 +177,12 @@ impl Editor {
|
|||||||
runtime_directory = "/usr/share/red/runtime".into();
|
runtime_directory = "/usr/share/red/runtime".into();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.env.set_global_value(
|
self.env
|
||||||
"red/runtime-directory",
|
.set_global_value(
|
||||||
StringValue::from(format!("{}", runtime_directory.display())),
|
"red/runtime-directory",
|
||||||
);
|
StringValue::from(format!("{}", runtime_directory.display())),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
self.evaluate_file(runtime_directory.join("core.lysp"));
|
self.evaluate_file(runtime_directory.join("core.lysp"));
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@@ -200,8 +224,6 @@ impl Editor {
|
|||||||
&key_hook,
|
&key_hook,
|
||||||
&[top_mode.into(), buffer_mode.into(), key.into()],
|
&[top_mode.into(), buffer_mode.into(), key.into()],
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
self.state.borrow_mut().set_message("No root key hook set!");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use crate::{
|
|||||||
|
|
||||||
pub mod convert;
|
pub mod convert;
|
||||||
pub use convert::{AsValue, FromValue, Movement};
|
pub use convert::{AsValue, FromValue, Movement};
|
||||||
use libterm::Color;
|
use libterm::{Clear, Color};
|
||||||
use lysp::{
|
use lysp::{
|
||||||
error::MachineError,
|
error::MachineError,
|
||||||
vm::{
|
vm::{
|
||||||
@@ -23,7 +23,7 @@ use lysp::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn editor_api(editor: &mut Editor) {
|
fn editor_api(editor: &mut Editor) {
|
||||||
editor.defun("red/shell-command", |_, _, state, args| {
|
editor.defun("red/shell-command", |_, env, _, args| {
|
||||||
let [command, args @ ..] = args else {
|
let [command, args @ ..] = args else {
|
||||||
return Err(MachineError::InvalidArgumentCount.into());
|
return Err(MachineError::InvalidArgumentCount.into());
|
||||||
};
|
};
|
||||||
@@ -40,7 +40,9 @@ fn editor_api(editor: &mut Editor) {
|
|||||||
if let Ok(text) = std::str::from_utf8(&output.stdout[..]) {
|
if let Ok(text) = std::str::from_utf8(&output.stdout[..]) {
|
||||||
let text = text.trim();
|
let text = text.trim();
|
||||||
let text = text.rsplit_once('\n').map(|(_, v)| v).unwrap_or(text);
|
let text = text.rsplit_once('\n').map(|(_, v)| v).unwrap_or(text);
|
||||||
state.borrow_mut().set_status(text);
|
|
||||||
|
env.set_global_value("_red/current-status", Value::String(text.into()))
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
Ok(Value::Nil)
|
Ok(Value::Nil)
|
||||||
} else {
|
} else {
|
||||||
@@ -80,55 +82,11 @@ fn editor_api(editor: &mut Editor) {
|
|||||||
[] => false,
|
[] => false,
|
||||||
[arg, ..] => arg.is_trueish(),
|
[arg, ..] => arg.is_trueish(),
|
||||||
};
|
};
|
||||||
state.borrow_mut().exit(force);
|
state.borrow_mut().exit(force)?;
|
||||||
Ok(Value::Nil)
|
Ok(Value::Nil)
|
||||||
});
|
});
|
||||||
editor.defun("red/command", |_, _, state, _| {
|
editor.defun("red/cursor-to-buffer", |_, _, state, _| {
|
||||||
let command = state.borrow().command.clone();
|
state.borrow_mut().cursor_to_buffer().ok();
|
||||||
Ok(StringValue::from(command).into())
|
|
||||||
});
|
|
||||||
editor.defun("red/set-command", |_, _, state, args| {
|
|
||||||
let [command] = args else {
|
|
||||||
return Err(MachineError::InvalidArgumentCount.into());
|
|
||||||
};
|
|
||||||
let command = StringValue::try_from_value(command)?;
|
|
||||||
state.borrow_mut().command = (*command).to_owned();
|
|
||||||
Ok(Value::Nil)
|
|
||||||
});
|
|
||||||
editor.defun("red/message", |_, _, state, args| {
|
|
||||||
let mut message = String::new();
|
|
||||||
for (i, arg) in args.iter().enumerate() {
|
|
||||||
if i != 0 {
|
|
||||||
message.push(' ');
|
|
||||||
}
|
|
||||||
match arg {
|
|
||||||
Value::String(value) => {
|
|
||||||
message.push_str(value.as_ref());
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
message.push_str(&format!("{arg}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
state.borrow_mut().set_message(message);
|
|
||||||
Ok(Value::Nil)
|
|
||||||
});
|
|
||||||
editor.defun("red/status", |_, _, state, args| {
|
|
||||||
let mut message = String::new();
|
|
||||||
for (i, arg) in args.iter().enumerate() {
|
|
||||||
if i != 0 {
|
|
||||||
message.push(' ');
|
|
||||||
}
|
|
||||||
match arg {
|
|
||||||
Value::String(value) => {
|
|
||||||
message.push_str(value.as_ref());
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
message.push_str(&format!("{arg}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
state.borrow_mut().set_status(message);
|
|
||||||
Ok(Value::Nil)
|
Ok(Value::Nil)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -288,6 +246,15 @@ fn buffer_api(editor: &mut Editor) {
|
|||||||
Ok(Value::Nil)
|
Ok(Value::Nil)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
editor.defun("red/buffer/name", |_, _, state, _| {
|
||||||
|
let state = state.borrow();
|
||||||
|
let name = state.buffer().name();
|
||||||
|
if let Some(name) = name {
|
||||||
|
Ok(Value::String((&**name).into()))
|
||||||
|
} else {
|
||||||
|
Ok(Value::Nil)
|
||||||
|
}
|
||||||
|
});
|
||||||
editor.defun("red/buffer/mode", |_, _, state, _| {
|
editor.defun("red/buffer/mode", |_, _, state, _| {
|
||||||
let state = state.borrow();
|
let state = state.borrow();
|
||||||
let (top_mode, buffer_mode) = state.mode();
|
let (top_mode, buffer_mode) = state.mode();
|
||||||
@@ -304,14 +271,17 @@ fn buffer_api(editor: &mut Editor) {
|
|||||||
state.set_mode(target_mode);
|
state.set_mode(target_mode);
|
||||||
Ok(Value::Nil)
|
Ok(Value::Nil)
|
||||||
});
|
});
|
||||||
editor.defun("red/buffer/write", |_, _, state, args| {
|
editor.defun("red/buffer/write", |_, env, state, args| {
|
||||||
let path = match args {
|
let path = match args {
|
||||||
[] => None,
|
[] => None,
|
||||||
[path] => Some(StringValue::try_from_value(path)?),
|
[path] => Some(StringValue::try_from_value(path)?),
|
||||||
_ => return Err(MachineError::InvalidArgumentCount.into()),
|
_ => return Err(MachineError::InvalidArgumentCount.into()),
|
||||||
};
|
};
|
||||||
let mut state = state.borrow_mut();
|
let mut state = state.borrow_mut();
|
||||||
state.write_buffer(path.as_deref())?;
|
if let Some(status) = state.write_buffer(path.as_deref())? {
|
||||||
|
env.set_global_value("_red/current-status", Value::String(status.into()))
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
Ok(Value::Nil)
|
Ok(Value::Nil)
|
||||||
});
|
});
|
||||||
editor.defun("red/buffer/open", |_, _, state, args| {
|
editor.defun("red/buffer/open", |_, _, state, args| {
|
||||||
@@ -358,6 +328,10 @@ fn buffer_api(editor: &mut Editor) {
|
|||||||
state.buffer_mut().newline_after(break_line);
|
state.buffer_mut().newline_after(break_line);
|
||||||
Ok(Value::Nil)
|
Ok(Value::Nil)
|
||||||
});
|
});
|
||||||
|
editor.defun("red/buffer/modified?", |_, _, state, _| {
|
||||||
|
let state = state.borrow();
|
||||||
|
Ok(state.buffer().is_modified().into())
|
||||||
|
});
|
||||||
editor.defun("red/buffer/kill-line", |_, _, state, _| {
|
editor.defun("red/buffer/kill-line", |_, _, state, _| {
|
||||||
let mut state = state.borrow_mut();
|
let mut state = state.borrow_mut();
|
||||||
state.kill_current_line();
|
state.kill_current_line();
|
||||||
@@ -378,6 +352,16 @@ fn buffer_api(editor: &mut Editor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn term_api(editor: &mut Editor) {
|
fn term_api(editor: &mut Editor) {
|
||||||
|
editor.defun("term/reset", |_, _, state, _| {
|
||||||
|
let mut state = state.borrow_mut();
|
||||||
|
state.term.reset_style().ok();
|
||||||
|
Ok(Value::Nil)
|
||||||
|
});
|
||||||
|
editor.defun("term/clear-line", |_, _, state, _| {
|
||||||
|
let mut state = state.borrow_mut();
|
||||||
|
state.term.clear(Clear::LineToEnd).ok();
|
||||||
|
Ok(Value::Nil)
|
||||||
|
});
|
||||||
editor.defun("term/fg-color", |_, _, state, args| {
|
editor.defun("term/fg-color", |_, _, state, args| {
|
||||||
let [color] = args else {
|
let [color] = args else {
|
||||||
return Err(MachineError::InvalidArgumentCount.into());
|
return Err(MachineError::InvalidArgumentCount.into());
|
||||||
|
|||||||
@@ -15,9 +15,6 @@ use crate::{
|
|||||||
pub struct State {
|
pub struct State {
|
||||||
pub(super) term: Term,
|
pub(super) term: Term,
|
||||||
buffer: Buffer,
|
buffer: Buffer,
|
||||||
pub(super) command: String,
|
|
||||||
message: Option<String>,
|
|
||||||
status: Option<String>,
|
|
||||||
top_mode: TopMode,
|
top_mode: TopMode,
|
||||||
config: Rc<RefCell<EditorConfig>>,
|
config: Rc<RefCell<EditorConfig>>,
|
||||||
running: bool,
|
running: bool,
|
||||||
@@ -43,9 +40,6 @@ impl State {
|
|||||||
Ok(Self {
|
Ok(Self {
|
||||||
number_width: buffer.number_width(),
|
number_width: buffer.number_width(),
|
||||||
top_mode: TopMode::Normal,
|
top_mode: TopMode::Normal,
|
||||||
message: None,
|
|
||||||
status: None,
|
|
||||||
command: String::new(),
|
|
||||||
running: true,
|
running: true,
|
||||||
buffer,
|
buffer,
|
||||||
term,
|
term,
|
||||||
@@ -76,26 +70,19 @@ impl State {
|
|||||||
Some(self.buffer.get_terminal_cursor(&self.config.borrow()))
|
Some(self.buffer.get_terminal_cursor(&self.config.borrow()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exit(&mut self, force: bool) {
|
pub fn exit(&mut self, force: bool) -> Result<(), Error> {
|
||||||
if self.buffer.is_modified() && !force {
|
if self.buffer.is_modified() && !force {
|
||||||
self.message = Some("Buffer has unsaved changes. Use :q! to force-exit".into());
|
Err(Error::UnsavedBuffer("Use :q! to force-exit"))
|
||||||
return;
|
} else {
|
||||||
|
self.running = false;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
self.running = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exited(&self) -> bool {
|
pub fn exited(&self) -> bool {
|
||||||
!self.running
|
!self.running
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_status<S: Into<String>>(&mut self, status: S) {
|
|
||||||
self.status.replace(status.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_message<S: Into<String>>(&mut self, message: S) {
|
|
||||||
self.message.replace(message.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_number(&mut self) -> Result<(), Error> {
|
fn display_number(&mut self) -> Result<(), Error> {
|
||||||
let start = self.buffer.row_offset();
|
let start = self.buffer.row_offset();
|
||||||
let end = self.buffer.len();
|
let end = self.buffer.len();
|
||||||
@@ -131,85 +118,12 @@ impl State {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_modeline(&mut self) -> Result<(), Error> {
|
pub fn cursor_to_buffer(&mut self) -> Result<(), Error> {
|
||||||
self.term
|
let config = self.config.borrow();
|
||||||
.set_cursor_position(self.buffer.height(), 0)
|
self.buffer.set_terminal_cursor(&config, &mut self.term)
|
||||||
.map_err(Error::TerminalError)?;
|
|
||||||
|
|
||||||
let bg = match (self.top_mode, self.buffer.mode()) {
|
|
||||||
(TopMode::Normal, Mode::Normal) => Color::Yellow,
|
|
||||||
(TopMode::Normal, Mode::Insert) => Color::Cyan,
|
|
||||||
(TopMode::Command, _) => Color::Green,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.term.set_background(bg).map_err(Error::TerminalError)?;
|
|
||||||
self.term
|
|
||||||
.set_foreground(Color::Black)
|
|
||||||
.map_err(Error::TerminalError)?;
|
|
||||||
|
|
||||||
match self.top_mode {
|
|
||||||
TopMode::Normal => {
|
|
||||||
write!(self.term, " {} ", self.buffer.mode().as_str())
|
|
||||||
.map_err(Error::TerminalFmtError)?;
|
|
||||||
|
|
||||||
if self.buffer.is_modified() {
|
|
||||||
self.term
|
|
||||||
.set_background(Color::Magenta)
|
|
||||||
.map_err(Error::TerminalError)?;
|
|
||||||
self.term
|
|
||||||
.set_foreground(Color::Default)
|
|
||||||
.map_err(Error::TerminalError)?;
|
|
||||||
} else {
|
|
||||||
self.term
|
|
||||||
.set_foreground(Color::Green)
|
|
||||||
.map_err(Error::TerminalError)?;
|
|
||||||
self.term
|
|
||||||
.set_background(Color::Default)
|
|
||||||
.map_err(Error::TerminalError)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TopMode::Command => {
|
|
||||||
write!(self.term, " COMMAND ").map_err(Error::TerminalFmtError)?;
|
|
||||||
|
|
||||||
self.term
|
|
||||||
.set_foreground(Color::Green)
|
|
||||||
.map_err(Error::TerminalError)?;
|
|
||||||
self.term
|
|
||||||
.set_background(Color::Default)
|
|
||||||
.map_err(Error::TerminalError)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = self
|
|
||||||
.buffer
|
|
||||||
.name()
|
|
||||||
.map(String::as_str)
|
|
||||||
.unwrap_or("<unnamed>");
|
|
||||||
write!(self.term, " {}", name).map_err(Error::TerminalFmtError)?;
|
|
||||||
self.term
|
|
||||||
.clear(Clear::LineToEnd)
|
|
||||||
.map_err(Error::TerminalError)?;
|
|
||||||
|
|
||||||
self.term.reset_style().map_err(Error::TerminalError)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finish_display(&mut self) -> Result<(), Error> {
|
pub fn finish_display(&mut self) -> Result<(), Error> {
|
||||||
let config = self.config.borrow();
|
|
||||||
|
|
||||||
match self.top_mode {
|
|
||||||
TopMode::Normal => {
|
|
||||||
self.buffer.set_terminal_cursor(&config, &mut self.term)?;
|
|
||||||
}
|
|
||||||
TopMode::Command => {
|
|
||||||
self.term
|
|
||||||
.set_cursor_position(self.buffer.height() + 1, 0)
|
|
||||||
.map_err(Error::TerminalError)?;
|
|
||||||
write!(self.term, ":{}", self.command.as_str()).map_err(Error::TerminalFmtError)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.term.flush().map_err(Error::TerminalError)?;
|
self.term.flush().map_err(Error::TerminalError)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -224,12 +138,14 @@ impl State {
|
|||||||
|
|
||||||
let (w, h) = self.term.size().map_err(Error::TerminalError)?;
|
let (w, h) = self.term.size().map_err(Error::TerminalError)?;
|
||||||
|
|
||||||
if config.number.clean() {
|
if config.number.clean() || config.bottom_margin.clean() {
|
||||||
if *config.number {
|
if *config.number {
|
||||||
let nw = self.buffer.number_width() + 3;
|
let nw = self.buffer.number_width() + 3;
|
||||||
self.buffer.resize(&config, nw, w - nw - 1, h - 2);
|
self.buffer
|
||||||
|
.resize(&config, nw, w - nw - 1, h - (*config.bottom_margin + 1));
|
||||||
} else {
|
} else {
|
||||||
self.buffer.resize(&config, 0, w - 1, h - 2);
|
self.buffer
|
||||||
|
.resize(&config, 0, w - 1, h - (*config.bottom_margin + 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,34 +156,11 @@ impl State {
|
|||||||
self.buffer
|
self.buffer
|
||||||
.display(&config, &mut self.term, &self.highlight)?;
|
.display(&config, &mut self.term, &self.highlight)?;
|
||||||
|
|
||||||
if self.top_mode != TopMode::Command
|
|
||||||
&& let Some(status) = &self.status
|
|
||||||
{
|
|
||||||
self.term
|
|
||||||
.set_cursor_position(self.buffer().height() + 1, 0)
|
|
||||||
.map_err(Error::TerminalError)?;
|
|
||||||
self.term
|
|
||||||
.write_str(status.as_str())
|
|
||||||
.map_err(Error::TerminalFmtError)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(msg) = &self.message {
|
|
||||||
self.term
|
|
||||||
.set_cursor_position(self.buffer.height(), 0)
|
|
||||||
.map_err(Error::TerminalError)?;
|
|
||||||
self.term.write_str(msg).map_err(Error::TerminalFmtError)?;
|
|
||||||
self.term.flush().map_err(Error::TerminalError)?;
|
|
||||||
return Ok((w, h));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.display_modeline()?;
|
|
||||||
|
|
||||||
Ok((w, h))
|
Ok((w, h))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_mode(&mut self, target: SetMode) {
|
pub fn set_mode(&mut self, target: SetMode) {
|
||||||
self.top_mode = TopMode::Normal;
|
self.top_mode = TopMode::Normal;
|
||||||
self.message = None;
|
|
||||||
self.buffer.set_mode(&self.config.borrow(), target);
|
self.buffer.set_mode(&self.config.borrow(), target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,8 +170,6 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.top_mode = target;
|
self.top_mode = target;
|
||||||
self.message = None;
|
|
||||||
self.command.clear();
|
|
||||||
self.buffer.set_mode(&self.config.borrow(), SetMode::Normal);
|
self.buffer.set_mode(&self.config.borrow(), SetMode::Normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,7 +214,10 @@ impl State {
|
|||||||
self.buffer.reopen(path).map_err(Error::OpenError)
|
self.buffer.reopen(path).map_err(Error::OpenError)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_buffer<P: AsRef<Path>>(&mut self, path: Option<P>) -> Result<(), Error> {
|
pub fn write_buffer<P: AsRef<Path>>(
|
||||||
|
&mut self,
|
||||||
|
path: Option<P>,
|
||||||
|
) -> Result<Option<String>, Error> {
|
||||||
if path.is_some() && self.buffer.is_modified() && self.buffer.path().is_some() {
|
if path.is_some() && self.buffer.is_modified() && self.buffer.path().is_some() {
|
||||||
return Err(Error::UnsavedBuffer(
|
return Err(Error::UnsavedBuffer(
|
||||||
"Use :w! FILE to force write to another file",
|
"Use :w! FILE to force write to another file",
|
||||||
@@ -337,10 +231,10 @@ impl State {
|
|||||||
|
|
||||||
if let Some(name) = self.buffer.name() {
|
if let Some(name) = self.buffer.name() {
|
||||||
let status = format!("{name:?} written");
|
let status = format!("{name:?} written");
|
||||||
self.set_status(status);
|
Ok(Some(status))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wait_for_events(&mut self) -> Result<TermKey, Error> {
|
pub fn wait_for_events(&mut self) -> Result<TermKey, Error> {
|
||||||
|
|||||||
Reference in New Issue
Block a user