From dc6ad33f017bb92968ff71f5171ff04e33f3c4d3 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Sun, 1 Dec 2024 21:56:49 +0000
Subject: [PATCH 01/51] version bumps
---
Cargo.lock | 37 +++++++++++++++----------------------
Cargo.toml | 2 +-
2 files changed, 16 insertions(+), 23 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 34c53396..957611b4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -56,9 +56,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
-version = "1.2.1"
+version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47"
+checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc"
dependencies = [
"shlex",
]
@@ -169,12 +169,6 @@ dependencies = [
"wasi",
]
-[[package]]
-name = "hermit-abi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
-
[[package]]
name = "home"
version = "0.5.9"
@@ -216,9 +210,9 @@ dependencies = [
[[package]]
name = "libc"
-version = "0.2.166"
+version = "0.2.167"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36"
+checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
[[package]]
name = "libredox"
@@ -279,11 +273,10 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "mio"
-version = "1.0.2"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
+checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
- "hermit-abi",
"libc",
"log",
"wasi",
@@ -292,9 +285,9 @@ dependencies = [
[[package]]
name = "mlua"
-version = "0.10.1"
+version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ae9546e4a268c309804e8bbb7526e31cbfdedca7cd60ac1b987d0b212e0d876"
+checksum = "9ea43c3ffac2d0798bd7128815212dd78c98316b299b7a902dabef13dc7b6b8d"
dependencies = [
"bstr",
"either",
@@ -306,9 +299,9 @@ dependencies = [
[[package]]
name = "mlua-sys"
-version = "0.6.5"
+version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efa6bf1a64f06848749b7e7727417f4ec2121599e2a10ef0a8a3888b0e9a5a0d"
+checksum = "63a11d485edf0f3f04a508615d36c7d50d299cf61a7ee6d3e2530651e0a31771"
dependencies = [
"cc",
"cfg-if",
@@ -340,7 +333,7 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "ox"
-version = "0.7.3"
+version = "0.7.4"
dependencies = [
"alinio",
"base64",
@@ -500,9 +493,9 @@ dependencies = [
[[package]]
name = "rustc-hash"
-version = "2.0.0"
+version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
+checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
[[package]]
name = "rustix"
@@ -608,9 +601,9 @@ checksum = "cc0db74f9ee706e039d031a560bd7d110c7022f016051b3d33eeff9583e3e67a"
[[package]]
name = "syn"
-version = "2.0.89"
+version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
+checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
diff --git a/Cargo.toml b/Cargo.toml
index 667bb5d3..8f6f41f3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,7 +6,7 @@ members = [
[package]
name = "ox"
-version = "0.7.3"
+version = "0.7.4"
edition = "2021"
authors = ["Curlpipe <11898833+curlpipe@users.noreply.github.com>"]
description = "A simple but flexible text editor."
From 7aa380da5bb3062b9180ffee5222611e98a80ecb Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Mon, 2 Dec 2024 00:33:02 +0000
Subject: [PATCH 02/51] Added very very early file tree logic (and made
everything non-doc-safe)
---
config/.oxrc | 4 +
src/config/editor.rs | 252 +++++++++++++++++++++++--------------
src/editor/cursor.rs | 116 ++++++++++-------
src/editor/documents.rs | 30 +++--
src/editor/editing.rs | 271 +++++++++++++++++++++++-----------------
src/editor/filetree.rs | 52 ++++++++
src/editor/interface.rs | 22 ++--
src/editor/mod.rs | 159 +++++++++++------------
src/editor/mouse.rs | 229 ++++++++++++++++++---------------
src/editor/scanning.rs | 120 ++++++++++--------
src/main.rs | 4 +-
11 files changed, 751 insertions(+), 508 deletions(-)
create mode 100644 src/editor/filetree.rs
diff --git a/config/.oxrc b/config/.oxrc
index 542eedbe..e162c703 100644
--- a/config/.oxrc
+++ b/config/.oxrc
@@ -276,6 +276,10 @@ event_mapping = {
editor:macro_record_stop()
editor:display_info("Macro recorded")
end,
+ -- File Tree
+ ["ctrl_space"] = function()
+ editor:toggle_file_tree()
+ end,
}
-- Define user-defined commands
diff --git a/src/config/editor.rs b/src/config/editor.rs
index 95222945..c8f15622 100644
--- a/src/config/editor.rs
+++ b/src/config/editor.rs
@@ -24,7 +24,7 @@ impl LuaUserData for Editor {
if let Some(doc) = editor.try_doc() {
let loc = doc.cursor.selection_end;
Ok(Some(LuaLoc {
- x: editor.doc().character_idx(&loc),
+ x: doc.character_idx(&loc),
y: loc.y + 1,
}))
} else {
@@ -189,16 +189,21 @@ impl LuaUserData for Editor {
Ok(())
});
methods.add_method_mut("remove_word", |_, editor, ()| {
- let _ = editor.doc_mut().delete_word();
- editor.update_highlighter();
- editor.hl_edit(editor.doc().loc().y);
+ if let Some(doc) = editor.try_doc_mut() {
+ let _ = doc.delete_word();
+ let y = doc.loc().y;
+ editor.update_highlighter();
+ editor.hl_edit(y);
+ }
Ok(())
});
// Cursor moving
methods.add_method_mut("move_to", |_, editor, (x, y): (usize, usize)| {
- let y = y.saturating_sub(1);
- editor.doc_mut().move_to(&Loc { y, x });
- editor.update_highlighter();
+ if let Some(doc) = editor.try_doc_mut() {
+ let y = y.saturating_sub(1);
+ doc.move_to(&Loc { y, x });
+ editor.update_highlighter();
+ }
Ok(())
});
methods.add_method_mut("move_up", |_, editor, ()| {
@@ -222,33 +227,45 @@ impl LuaUserData for Editor {
Ok(())
});
methods.add_method_mut("move_home", |_, editor, ()| {
- editor.doc_mut().move_home();
- editor.update_highlighter();
+ if let Some(doc) = editor.try_doc_mut() {
+ doc.move_home();
+ editor.update_highlighter();
+ }
Ok(())
});
methods.add_method_mut("move_end", |_, editor, ()| {
- editor.doc_mut().move_end();
- editor.update_highlighter();
+ if let Some(doc) = editor.try_doc_mut() {
+ doc.move_end();
+ editor.update_highlighter();
+ }
Ok(())
});
methods.add_method_mut("move_page_up", |_, editor, ()| {
- editor.doc_mut().move_page_up();
- editor.update_highlighter();
+ if let Some(doc) = editor.try_doc_mut() {
+ doc.move_page_up();
+ editor.update_highlighter();
+ }
Ok(())
});
methods.add_method_mut("move_page_down", |_, editor, ()| {
- editor.doc_mut().move_page_down();
- editor.update_highlighter();
+ if let Some(doc) = editor.try_doc_mut() {
+ doc.move_page_down();
+ editor.update_highlighter();
+ }
Ok(())
});
methods.add_method_mut("move_top", |_, editor, ()| {
- editor.doc_mut().move_top();
- editor.update_highlighter();
+ if let Some(doc) = editor.try_doc_mut() {
+ doc.move_top();
+ editor.update_highlighter();
+ }
Ok(())
});
methods.add_method_mut("move_bottom", |_, editor, ()| {
- editor.doc_mut().move_bottom();
- editor.update_highlighter();
+ if let Some(doc) = editor.try_doc_mut() {
+ doc.move_bottom();
+ editor.update_highlighter();
+ }
Ok(())
});
methods.add_method_mut("move_previous_word", |_, editor, ()| {
@@ -262,21 +279,29 @@ impl LuaUserData for Editor {
Ok(())
});
methods.add_method_mut("cursor_snap", |_, editor, ()| {
- editor.doc_mut().old_cursor = editor.doc().loc().x;
+ if let Some(doc) = editor.try_doc_mut() {
+ doc.old_cursor = doc.loc().x;
+ }
Ok(())
});
methods.add_method_mut("move_line_up", |_, editor, ()| {
- let _ = editor.doc_mut().swap_line_up();
- editor.hl_edit(editor.doc().loc().y);
- editor.hl_edit(editor.doc().loc().y + 1);
- editor.update_highlighter();
+ if let Some(doc) = editor.try_doc_mut() {
+ let _ = doc.swap_line_up();
+ let y = doc.loc().y;
+ editor.hl_edit(y);
+ editor.hl_edit(y + 1);
+ editor.update_highlighter();
+ }
Ok(())
});
methods.add_method_mut("move_line_down", |_, editor, ()| {
- let _ = editor.doc_mut().swap_line_down();
- editor.hl_edit(editor.doc().loc().y.saturating_sub(1));
- editor.hl_edit(editor.doc().loc().y);
- editor.update_highlighter();
+ if let Some(doc) = editor.try_doc_mut() {
+ let _ = doc.swap_line_down();
+ let y = doc.loc().y;
+ editor.hl_edit(y.saturating_sub(1));
+ editor.hl_edit(y);
+ editor.update_highlighter();
+ }
Ok(())
});
// Cursor selection and clipboard
@@ -306,17 +331,23 @@ impl LuaUserData for Editor {
Ok(())
});
methods.add_method_mut("select_to", |_, editor, (x, y): (usize, usize)| {
- let y = y.saturating_sub(1);
- editor.doc_mut().select_to(&Loc { y, x });
- editor.update_highlighter();
+ if let Some(doc) = editor.try_doc_mut() {
+ let y = y.saturating_sub(1);
+ doc.select_to(&Loc { y, x });
+ editor.update_highlighter();
+ }
Ok(())
});
methods.add_method_mut("cancel_selection", |_, editor, ()| {
- editor.doc_mut().cancel_selection();
+ if let Some(doc) = editor.try_doc_mut() {
+ doc.cancel_selection();
+ }
Ok(())
});
methods.add_method_mut("cursor_to_viewport", |_, editor, ()| {
- editor.doc_mut().bring_cursor_in_viewport();
+ if let Some(doc) = editor.try_doc_mut() {
+ doc.bring_cursor_in_viewport();
+ }
Ok(())
});
methods.add_method_mut("cut", |_, editor, ()| {
@@ -341,72 +372,90 @@ impl LuaUserData for Editor {
methods.add_method_mut(
"insert_at",
|_, editor, (text, x, y): (String, usize, usize)| {
- editor.plugin_active = true;
- let y = y.saturating_sub(1);
- let location = editor.doc_mut().char_loc();
- editor.doc_mut().move_to(&Loc { y, x });
- for ch in text.chars() {
- if let Err(err) = editor.character(ch) {
- editor.feedback = Feedback::Error(err.to_string());
+ if editor.try_doc().is_some() {
+ editor.plugin_active = true;
+ let y = y.saturating_sub(1);
+ let location = editor.try_doc().unwrap().char_loc();
+ if let Some(doc) = editor.try_doc_mut() {
+ doc.move_to(&Loc { y, x });
+ }
+ for ch in text.chars() {
+ if let Err(err) = editor.character(ch) {
+ editor.feedback = Feedback::Error(err.to_string());
+ }
}
+ if let Some(doc) = editor.try_doc_mut() {
+ doc.move_to(&location);
+ }
+ editor.update_highlighter();
+ editor.plugin_active = false;
}
- editor.doc_mut().move_to(&location);
- editor.update_highlighter();
- editor.plugin_active = false;
Ok(())
},
);
methods.add_method_mut("remove_at", |_, editor, (x, y): (usize, usize)| {
- editor.plugin_active = true;
- let y = y.saturating_sub(1);
- let location = editor.doc_mut().char_loc();
- editor.doc_mut().move_to(&Loc { y, x });
- if let Err(err) = editor.delete() {
- editor.feedback = Feedback::Error(err.to_string());
- }
- editor.doc_mut().move_to(&location);
- editor.update_highlighter();
- editor.plugin_active = false;
- Ok(())
- });
- methods.add_method_mut("insert_line_at", |_, editor, (text, y): (String, usize)| {
- editor.plugin_active = true;
- let y = y.saturating_sub(1);
- let location = editor.doc_mut().char_loc();
- if y < editor.doc().len_lines() {
- editor.doc_mut().move_to_y(y);
- editor.doc_mut().move_home();
- if let Err(err) = editor.enter() {
- editor.feedback = Feedback::Error(err.to_string());
+ if editor.try_doc().is_some() {
+ editor.plugin_active = true;
+ let y = y.saturating_sub(1);
+ let location = editor.try_doc().unwrap().char_loc();
+ if let Some(doc) = editor.try_doc_mut() {
+ doc.move_to(&Loc { y, x });
}
- editor.up();
- } else {
- editor.doc_mut().move_bottom();
- if let Err(err) = editor.enter() {
+ if let Err(err) = editor.delete() {
editor.feedback = Feedback::Error(err.to_string());
}
+ if let Some(doc) = editor.try_doc_mut() {
+ doc.move_to(&location);
+ }
+ editor.update_highlighter();
+ editor.plugin_active = false;
}
- for ch in text.chars() {
- if let Err(err) = editor.character(ch) {
- editor.feedback = Feedback::Error(err.to_string());
+ Ok(())
+ });
+ methods.add_method_mut("insert_line_at", |_, editor, (text, y): (String, usize)| {
+ if editor.try_doc().is_some() {
+ editor.plugin_active = true;
+ let y = y.saturating_sub(1);
+ let location = editor.try_doc().unwrap().char_loc();
+ if let Some(doc) = editor.try_doc_mut() {
+ if y < doc.len_lines() {
+ doc.move_to_y(y);
+ doc.move_home();
+ if let Err(err) = editor.enter() {
+ editor.feedback = Feedback::Error(err.to_string());
+ }
+ editor.up();
+ } else {
+ doc.move_bottom();
+ if let Err(err) = editor.enter() {
+ editor.feedback = Feedback::Error(err.to_string());
+ }
+ }
+ for ch in text.chars() {
+ if let Err(err) = editor.character(ch) {
+ editor.feedback = Feedback::Error(err.to_string());
+ }
+ }
}
+ editor.try_doc_mut().unwrap().move_to(&location);
+ editor.update_highlighter();
+ editor.plugin_active = false;
}
- editor.doc_mut().move_to(&location);
- editor.update_highlighter();
- editor.plugin_active = false;
Ok(())
});
methods.add_method_mut("remove_line_at", |_, editor, y: usize| {
- editor.plugin_active = true;
- let y = y.saturating_sub(1);
- let location = editor.doc_mut().char_loc();
- editor.doc_mut().move_to_y(y);
- if let Err(err) = editor.delete_line() {
- editor.feedback = Feedback::Error(err.to_string());
+ if editor.try_doc().is_some() {
+ editor.plugin_active = true;
+ let y = y.saturating_sub(1);
+ let location = editor.try_doc().unwrap().char_loc();
+ editor.try_doc_mut().unwrap().move_to_y(y);
+ if let Err(err) = editor.delete_line() {
+ editor.feedback = Feedback::Error(err.to_string());
+ }
+ editor.try_doc_mut().unwrap().move_to(&location);
+ editor.update_highlighter();
+ editor.plugin_active = false;
}
- editor.doc_mut().move_to(&location);
- editor.update_highlighter();
- editor.plugin_active = false;
Ok(())
});
methods.add_method_mut("get", |_, editor, ()| {
@@ -639,32 +688,40 @@ impl LuaUserData for Editor {
});
methods.add_method_mut("move_next_match", |_, editor, query: String| {
editor.next_match(&query);
- editor.doc_mut().cancel_selection();
+ if let Some(doc) = editor.try_doc_mut() {
+ doc.cancel_selection();
+ }
editor.update_highlighter();
Ok(())
});
methods.add_method_mut("move_previous_match", |_, editor, query: String| {
editor.prev_match(&query);
- editor.doc_mut().cancel_selection();
+ if let Some(doc) = editor.try_doc_mut() {
+ doc.cancel_selection();
+ }
editor.update_highlighter();
Ok(())
});
// Document state modification
methods.add_method_mut("set_read_only", |_, editor, status: bool| {
- editor.doc_mut().info.read_only = status;
+ if let Some(doc) = editor.try_doc_mut() {
+ doc.info.read_only = status;
+ }
Ok(())
});
methods.add_method_mut("set_file_type", |_, editor, name: String| {
- let doc = config!(editor.config, document);
- if let Some(file_type) = doc.file_types.get_name(&name) {
- let mut highlighter = file_type.get_highlighter(&editor.config, 4);
- highlighter.run(&editor.doc().lines);
- if let Some(file) = editor.files.get_mut(editor.ptr.clone()) {
- file.highlighter = highlighter;
- file.file_type = Some(file_type);
+ if let Some(actual_doc) = editor.try_doc() {
+ let doc = config!(editor.config, document);
+ if let Some(file_type) = doc.file_types.get_name(&name) {
+ let mut highlighter = file_type.get_highlighter(&editor.config, 4);
+ highlighter.run(&actual_doc.lines);
+ if let Some(file) = editor.files.get_mut(editor.ptr.clone()) {
+ file.highlighter = highlighter;
+ file.file_type = Some(file_type);
+ }
+ } else {
+ editor.feedback = Feedback::Error(format!("Invalid file type: {name}"));
}
- } else {
- editor.feedback = Feedback::Error(format!("Invalid file type: {name}"));
}
Ok(())
});
@@ -690,6 +747,11 @@ impl LuaUserData for Editor {
let _ = editor.render(lua);
Ok(())
});
+ // File Tree
+ methods.add_method_mut("toggle_file_tree", |_, editor, ()| {
+ editor.toggle_file_tree();
+ Ok(())
+ });
// Miscellaneous
methods.add_method_mut("open_command_line", |_, editor, ()| {
match editor.prompt("Command") {
diff --git a/src/editor/cursor.rs b/src/editor/cursor.rs
index 246279f4..516375bd 100644
--- a/src/editor/cursor.rs
+++ b/src/editor/cursor.rs
@@ -9,91 +9,113 @@ use super::Editor;
impl Editor {
/// Move the cursor up
pub fn select_up(&mut self) {
- self.doc_mut().select_up();
+ if let Some(doc) = self.try_doc_mut() {
+ doc.select_up();
+ }
}
/// Move the cursor down
pub fn select_down(&mut self) {
- self.doc_mut().select_down();
+ if let Some(doc) = self.try_doc_mut() {
+ doc.select_down();
+ }
}
/// Move the cursor left
pub fn select_left(&mut self) {
- let status = self.doc_mut().select_left();
- // Cursor wrapping if cursor hits the start of the line
let wrapping = config!(self.config, document).wrap_cursor;
- if status == Status::StartOfLine && self.doc().loc().y != 0 && wrapping {
- self.doc_mut().select_up();
- self.doc_mut().select_end();
+ if let Some(doc) = self.try_doc_mut() {
+ let status = doc.select_left();
+ // Cursor wrapping if cursor hits the start of the line
+ if status == Status::StartOfLine && doc.loc().y != 0 && wrapping {
+ doc.select_up();
+ doc.select_end();
+ }
}
}
/// Move the cursor right
pub fn select_right(&mut self) {
- let status = self.doc_mut().select_right();
- // Cursor wrapping if cursor hits the end of a line
let wrapping = config!(self.config, document).wrap_cursor;
- if status == Status::EndOfLine && wrapping {
- self.doc_mut().select_down();
- self.doc_mut().select_home();
+ if let Some(doc) = self.try_doc_mut() {
+ let status = doc.select_right();
+ // Cursor wrapping if cursor hits the end of a line
+ if status == Status::EndOfLine && wrapping {
+ doc.select_down();
+ doc.select_home();
+ }
}
}
/// Select the whole document
pub fn select_all(&mut self) {
- self.doc_mut().move_top();
- self.doc_mut().select_bottom();
+ if let Some(doc) = self.try_doc_mut() {
+ doc.move_top();
+ doc.select_bottom();
+ }
}
/// Move the cursor up
pub fn up(&mut self) {
- self.doc_mut().move_up();
+ if let Some(doc) = self.try_doc_mut() {
+ doc.move_up();
+ }
}
/// Move the cursor down
pub fn down(&mut self) {
- self.doc_mut().move_down();
+ if let Some(doc) = self.try_doc_mut() {
+ doc.move_down();
+ }
}
/// Move the cursor left
pub fn left(&mut self) {
- let status = self.doc_mut().move_left();
- // Cursor wrapping if cursor hits the start of the line
let wrapping = config!(self.config, document).wrap_cursor;
- if status == Status::StartOfLine && self.doc().loc().y != 0 && wrapping {
- self.doc_mut().move_up();
- self.doc_mut().move_end();
+ if let Some(doc) = self.try_doc_mut() {
+ let status = doc.move_left();
+ // Cursor wrapping if cursor hits the start of the line
+ if status == Status::StartOfLine && doc.loc().y != 0 && wrapping {
+ doc.move_up();
+ doc.move_end();
+ }
}
}
/// Move the cursor right
pub fn right(&mut self) {
- let status = self.doc_mut().move_right();
- // Cursor wrapping if cursor hits the end of a line
let wrapping = config!(self.config, document).wrap_cursor;
- if status == Status::EndOfLine && wrapping {
- self.doc_mut().move_down();
- self.doc_mut().move_home();
+ if let Some(doc) = self.try_doc_mut() {
+ let status = doc.move_right();
+ // Cursor wrapping if cursor hits the end of a line
+ if status == Status::EndOfLine && wrapping {
+ doc.move_down();
+ doc.move_home();
+ }
}
}
/// Move the cursor to the previous word in the line
pub fn prev_word(&mut self) {
- let status = self.doc_mut().move_prev_word();
let wrapping = config!(self.config, document).wrap_cursor;
- if status == Status::StartOfLine && wrapping {
- self.doc_mut().move_up();
- self.doc_mut().move_end();
+ if let Some(doc) = self.try_doc_mut() {
+ let status = doc.move_prev_word();
+ if status == Status::StartOfLine && wrapping {
+ doc.move_up();
+ doc.move_end();
+ }
}
}
/// Move the cursor to the next word in the line
pub fn next_word(&mut self) {
- let status = self.doc_mut().move_next_word();
let wrapping = config!(self.config, document).wrap_cursor;
- if status == Status::EndOfLine && wrapping {
- self.doc_mut().move_down();
- self.doc_mut().move_home();
+ if let Some(doc) = self.try_doc_mut() {
+ let status = doc.move_next_word();
+ if status == Status::EndOfLine && wrapping {
+ doc.move_down();
+ doc.move_home();
+ }
}
}
}
@@ -105,12 +127,14 @@ pub fn handle_multiple_cursors(
lua: &Lua,
original_loc: &Loc,
) -> Result<()> {
+ if ged!(&editor).try_doc().is_none() {
+ return Ok(());
+ }
let mut original_loc = *original_loc;
// Cache the state of the document
- let mut cursor = ged!(&editor).doc().cursor;
- // For each secondary cursor, replay the key event
+ let mut cursor = ged!(&editor).try_doc().unwrap().cursor;
+ let mut secondary_cursors = ged!(&editor).try_doc().unwrap().secondary_cursors.clone();
ged!(mut &editor).macro_man.playing = true;
- let mut secondary_cursors = ged!(&editor).doc().secondary_cursors.clone();
// Prevent interference
adjust_other_cursors(
&mut secondary_cursors,
@@ -124,12 +148,12 @@ pub fn handle_multiple_cursors(
while ptr < secondary_cursors.len() {
// Move to the secondary cursor position
let sec_cursor = secondary_cursors[ptr];
- ged!(mut &editor).doc_mut().move_to(&sec_cursor);
+ ged!(mut &editor).try_doc_mut().unwrap().move_to(&sec_cursor);
// Replay the event
- let old_loc = ged!(&editor).doc().char_loc();
+ let old_loc = ged!(&editor).try_doc().unwrap().char_loc();
handle_event(editor, event, lua)?;
// Prevent any interference
- let char_loc = ged!(&editor).doc().char_loc();
+ let char_loc = ged!(&editor).try_doc().unwrap().char_loc();
cursor.loc = adjust_other_cursors(
&mut secondary_cursors,
&old_loc,
@@ -142,15 +166,15 @@ pub fn handle_multiple_cursors(
// Move to the next secondary cursor
ptr += 1;
}
- ged!(mut &editor).doc_mut().secondary_cursors = secondary_cursors;
+ ged!(mut &editor).try_doc_mut().unwrap().secondary_cursors = secondary_cursors;
ged!(mut &editor).macro_man.playing = false;
// Restore back to the state of the document beforehand
// TODO: calculate char_ptr and old_cursor too
- ged!(mut &editor).doc_mut().cursor = cursor;
- let char_ptr = ged!(&editor).doc().character_idx(&cursor.loc);
- ged!(mut &editor).doc_mut().char_ptr = char_ptr;
- ged!(mut &editor).doc_mut().old_cursor = cursor.loc.x;
- ged!(mut &editor).doc_mut().cancel_selection();
+ ged!(mut &editor).try_doc_mut().unwrap().cursor = cursor;
+ let char_ptr = ged!(&editor).try_doc().unwrap().character_idx(&cursor.loc);
+ ged!(mut &editor).try_doc_mut().unwrap().char_ptr = char_ptr;
+ ged!(mut &editor).try_doc_mut().unwrap().old_cursor = cursor.loc.x;
+ ged!(mut &editor).try_doc_mut().unwrap().cancel_selection();
Ok(())
}
diff --git a/src/editor/documents.rs b/src/editor/documents.rs
index 4cea45bc..b9640a08 100644
--- a/src/editor/documents.rs
+++ b/src/editor/documents.rs
@@ -19,6 +19,8 @@ pub enum FileLayout {
Atom(Vec, usize),
/// Placeholder for an empty file split
None,
+ /// Representing a file tree
+ FileTree,
}
impl Default for FileLayout {
@@ -38,8 +40,8 @@ impl FileLayout {
pub fn span(&self, idx: Vec, size: Size, at: Loc) -> Span {
match self {
Self::None => vec![],
- // Atom: stretches from starting position through to end of it's container
- Self::Atom(_, _) => vec![(idx, at.y..at.y + size.h, at.x..at.x + size.w)],
+ // Atom and file trees: stretch from starting position through to end of their containers
+ Self::Atom(_, _) | Self::FileTree => vec![(idx, at.y..at.y + size.h, at.x..at.x + size.w)],
// SideBySide: distributes available container space to each sub-layout
Self::SideBySide(layouts) => {
let mut result = vec![];
@@ -153,7 +155,7 @@ impl FileLayout {
/// Work out how many files are currently open
pub fn len(&self) -> usize {
match self {
- Self::None => 0,
+ Self::None | Self::FileTree => 0,
Self::Atom(containers, _) => containers.len(),
Self::SideBySide(layouts) => layouts.iter().map(|(layout, _)| layout.len()).sum(),
Self::TopToBottom(layouts) => layouts.iter().map(|(layout, _)| layout.len()).sum(),
@@ -163,7 +165,7 @@ impl FileLayout {
/// Find a file container location from it's path
pub fn find(&self, idx: Vec, path: &str) -> Option<(Vec, usize)> {
match self {
- Self::None => None,
+ Self::None | Self::FileTree => None,
Self::Atom(containers, _) => {
// Scan this atom for any documents
for (ptr, container) in containers.iter().enumerate() {
@@ -193,7 +195,7 @@ impl FileLayout {
/// Get the `FileLayout` at a certain index
pub fn get_raw(&self, mut idx: Vec) -> Option<&FileLayout> {
match self {
- Self::None | Self::Atom(_, _) => Some(self),
+ Self::None | Self::Atom(_, _) | Self::FileTree => Some(self),
Self::SideBySide(layouts) => {
if idx.is_empty() {
Some(self)
@@ -219,7 +221,7 @@ impl FileLayout {
Some(self)
} else {
match self {
- Self::None | Self::Atom(_, _) => Some(self),
+ Self::None | Self::Atom(_, _) | Self::FileTree => Some(self),
Self::SideBySide(layouts) => {
let subidx = idx.remove(0);
layouts.get_mut(subidx)?.0.get_raw_mut(idx)
@@ -235,7 +237,7 @@ impl FileLayout {
/// Get the `FileLayout` at a certain index
pub fn set(&mut self, mut idx: Vec, fl: FileLayout) {
match self {
- Self::None | Self::Atom(_, _) => *self = fl,
+ Self::None | Self::Atom(_, _) | Self::FileTree => *self = fl,
Self::SideBySide(layouts) | Self::TopToBottom(layouts) => {
if idx.is_empty() {
*self = fl;
@@ -250,7 +252,7 @@ impl FileLayout {
/// Given an index, find the file containers in the tree
pub fn get_atom(&self, mut idx: Vec) -> Option<(&[FileContainer], usize)> {
match self {
- Self::None => None,
+ Self::None | Self::FileTree => None,
Self::Atom(containers, ptr) => Some((containers, *ptr)),
Self::SideBySide(layouts) => {
let subidx = idx.remove(0);
@@ -269,7 +271,7 @@ impl FileLayout {
mut idx: Vec,
) -> Option<(&mut Vec, &mut usize)> {
match self {
- Self::None => None,
+ Self::None | Self::FileTree => None,
Self::Atom(ref mut containers, ref mut ptr) => Some((containers, ptr)),
Self::SideBySide(layouts) => {
let subidx = idx.remove(0);
@@ -302,7 +304,7 @@ impl FileLayout {
/// In the currently active atom, move to a different document
pub fn move_to(&mut self, mut idx: Vec, ptr: usize) {
match self {
- Self::None => (),
+ Self::None | Self::FileTree => (),
Self::Atom(_, ref mut old_ptr) => *old_ptr = ptr,
Self::SideBySide(layouts) | Self::TopToBottom(layouts) => {
let subidx = idx.remove(0);
@@ -329,7 +331,7 @@ impl FileLayout {
// Determine behaviour based on parent
if let Some(parent) = self.get_raw_mut(at_parent) {
match parent {
- Self::None | Self::Atom(_, _) => unreachable!(),
+ Self::None | Self::Atom(_, _) | Self::FileTree => unreachable!(),
Self::SideBySide(layouts) | Self::TopToBottom(layouts) => {
// Get the proportion of what we're removing
let removed_prop = layouts[within_parent].1;
@@ -353,7 +355,7 @@ impl FileLayout {
/// Traverse the tree and return a list of indices to empty atoms
pub fn empty_atoms(&self, at: Vec) -> Option> {
match self {
- Self::None => None,
+ Self::None | Self::FileTree => None,
Self::Atom(fcs, _) => {
if fcs.is_empty() {
Some(at)
@@ -407,6 +409,7 @@ impl FileLayout {
new_ptr.push(0);
Self::TopToBottom(vec![(fl, 0.5), (old_fl.clone(), 0.5)])
}
+ Self::FileTree => return at,
};
self.set(at, new_fl);
}
@@ -423,6 +426,7 @@ impl FileLayout {
new_ptr.push(1);
Self::TopToBottom(vec![(old_fl.clone(), 0.5), (fl, 0.5)])
}
+ Self::FileTree => return at,
};
self.set(at, new_fl);
}
@@ -439,6 +443,7 @@ impl FileLayout {
new_ptr.push(0);
Self::SideBySide(vec![(fl, 0.5), (old_fl.clone(), 0.5)])
}
+ Self::FileTree => return at,
};
self.set(at, new_fl);
}
@@ -455,6 +460,7 @@ impl FileLayout {
new_ptr.push(1);
Self::SideBySide(vec![(old_fl.clone(), 0.5), (fl, 0.5)])
}
+ Self::FileTree => return at,
};
self.set(at, new_fl);
}
diff --git a/src/editor/editing.rs b/src/editor/editing.rs
index 32ea09d9..d49e2dc5 100644
--- a/src/editor/editing.rs
+++ b/src/editor/editing.rs
@@ -8,44 +8,50 @@ use super::Editor;
impl Editor {
/// Execute an edit event
pub fn exe(&mut self, ev: Event) -> Result<()> {
- let multi_cursors = self.doc().secondary_cursors.len() > 0;
- if !(self.plugin_active || self.pasting || self.macro_man.playing || multi_cursors) {
- let last_ev = self.doc().event_mgmt.last_event.as_ref();
- // If last event is present and the same as this one, commit
- let event_type_differs = last_ev.map(|e1| e1.same_type(&ev)) != Some(true);
- // If last event is present and on a different line from the previous, commit
- let event_on_different_line = last_ev.map(|e| e.loc().y == ev.loc().y) != Some(true);
- // Commit if necessary
- if event_type_differs || event_on_different_line {
- self.doc_mut().commit();
+ if self.try_doc().is_some() {
+ let multi_cursors = !self.try_doc().unwrap().secondary_cursors.is_empty();
+ if !(self.plugin_active || self.pasting || self.macro_man.playing || multi_cursors) {
+ let last_ev = self.try_doc().unwrap().event_mgmt.last_event.as_ref();
+ // If last event is present and the same as this one, commit
+ let event_type_differs = last_ev.map(|e1| e1.same_type(&ev)) != Some(true);
+ // If last event is present and on a different line from the previous, commit
+ let event_on_different_line = last_ev.map(|e| e.loc().y == ev.loc().y) != Some(true);
+ // Commit if necessary
+ if event_type_differs || event_on_different_line {
+ self.try_doc_mut().unwrap().commit();
+ }
+ } else if self.try_doc().unwrap().event_mgmt.history.is_empty() {
+ // If there is no initial commit and a plug-in changes things without commiting
+ // It can cause the initial state of the document to be lost
+ // This condition makes sure there is a copy to go back to if this is the case
+ self.try_doc_mut().unwrap().commit();
}
- } else if self.doc().event_mgmt.history.is_empty() {
- // If there is no initial commit and a plug-in changes things without commiting
- // It can cause the initial state of the document to be lost
- // This condition makes sure there is a copy to go back to if this is the case
- self.doc_mut().commit();
+ self.try_doc_mut().unwrap().exe(ev)?;
}
- self.doc_mut().exe(ev)?;
Ok(())
}
/// Insert a character into the document, creating a new row if editing
/// on the last line of the document
pub fn character(&mut self, ch: char) -> Result<()> {
- if !self.doc().is_selection_empty() && !self.doc().info.read_only {
- self.doc_mut().remove_selection();
- self.reload_highlight();
- }
- self.new_row()?;
- // Handle the character insertion
- if ch == '\n' {
- self.enter()?;
- } else {
- let loc = self.doc().char_loc();
- self.exe(Event::Insert(loc, ch.to_string()))?;
- if let Some(file) = self.files.get_mut(self.ptr.clone()) {
- if !file.doc.info.read_only {
- file.highlighter.edit(loc.y, &file.doc.lines[loc.y]);
+ if self.try_doc().is_some() {
+ let doc = self.try_doc().unwrap();
+ if !doc.is_selection_empty() && !doc.info.read_only {
+ self.try_doc_mut().unwrap().remove_selection();
+ self.reload_highlight();
+ }
+ self.new_row()?;
+ // Handle the character insertion
+ if ch == '\n' {
+ self.enter()?;
+ } else {
+ let doc = self.try_doc().unwrap();
+ let loc = doc.char_loc();
+ self.exe(Event::Insert(loc, ch.to_string()))?;
+ if let Some(file) = self.files.get_mut(self.ptr.clone()) {
+ if !file.doc.info.read_only {
+ file.highlighter.edit(loc.y, &file.doc.lines[loc.y]);
+ }
}
}
}
@@ -54,20 +60,22 @@ impl Editor {
/// Handle the return key
pub fn enter(&mut self) -> Result<()> {
- // Perform the changes
- if self.doc().loc().y == self.doc().len_lines() {
- // Enter pressed on the empty line at the bottom of the document
- self.new_row()?;
- } else {
- // Enter pressed in the start, middle or end of the line
- let loc = self.doc().char_loc();
- self.exe(Event::SplitDown(loc))?;
- if let Some(file) = self.files.get_mut(self.ptr.clone()) {
- if !file.doc.info.read_only {
- let line = &file.doc.lines[loc.y + 1];
- file.highlighter.insert_line(loc.y + 1, line);
- let line = &file.doc.lines[loc.y];
- file.highlighter.edit(loc.y, line);
+ if let Some(doc) = self.try_doc_mut() {
+ // Perform the changes
+ if doc.loc().y == doc.len_lines() {
+ // Enter pressed on the empty line at the bottom of the document
+ self.new_row()?;
+ } else {
+ // Enter pressed in the start, middle or end of the line
+ let loc = doc.char_loc();
+ self.exe(Event::SplitDown(loc))?;
+ if let Some(file) = self.files.get_mut(self.ptr.clone()) {
+ if !file.doc.info.read_only {
+ let line = &file.doc.lines[loc.y + 1];
+ file.highlighter.insert_line(loc.y + 1, line);
+ let line = &file.doc.lines[loc.y];
+ file.highlighter.edit(loc.y, line);
+ }
}
}
}
@@ -76,46 +84,51 @@ impl Editor {
/// Handle the backspace key
pub fn backspace(&mut self) -> Result<()> {
- if !self.doc().is_selection_empty() && !self.doc().info.read_only {
- // Removing a selection is significant and worth an undo commit
- self.doc_mut().commit();
- self.doc_mut().remove_selection();
- self.reload_highlight();
- return Ok(());
- }
- let mut c = self.doc().char_ptr;
- let on_first_line = self.doc().loc().y == 0;
- let out_of_range = self.doc().out_of_range(0, self.doc().loc().y).is_err();
- if c == 0 && !on_first_line && !out_of_range {
- // Backspace was pressed on the start of the line, move line to the top
- self.new_row()?;
- let mut loc = self.doc().char_loc();
- let file = self.files.get_mut(self.ptr.clone()).unwrap();
- if !file.doc.info.read_only {
- self.highlighter().remove_line(loc.y);
+ if self.try_doc().is_some() {
+ let doc = self.try_doc().unwrap();
+ if !doc.is_selection_empty() && !doc.info.read_only {
+ // Removing a selection is significant and worth an undo commit
+ let doc = self.try_doc_mut().unwrap();
+ doc.commit();
+ doc.remove_selection();
+ self.reload_highlight();
+ return Ok(());
}
- loc.y = loc.y.saturating_sub(1);
- let file = self.files.get_mut(self.ptr.clone()).unwrap();
- loc.x = file.doc.line(loc.y).unwrap().chars().count();
- self.exe(Event::SpliceUp(loc))?;
- let file = self.files.get_mut(self.ptr.clone()).unwrap();
- let line = &file.doc.lines[loc.y];
- if !file.doc.info.read_only {
- file.highlighter.edit(loc.y, line);
- }
- } else if !(c == 0 && on_first_line) {
- // Backspace was pressed in the middle of the line, delete the character
- c = c.saturating_sub(1);
- if let Some(line) = self.doc().line(self.doc().loc().y) {
- if let Some(ch) = line.chars().nth(c) {
- let loc = Loc {
- x: c,
- y: self.doc().loc().y,
- };
- self.exe(Event::Delete(loc, ch.to_string()))?;
- let file = self.files.get_mut(self.ptr.clone()).unwrap();
- if !file.doc.info.read_only {
- file.highlighter.edit(loc.y, &file.doc.lines[loc.y]);
+ let doc = self.try_doc().unwrap();
+ let mut c = doc.char_ptr;
+ let on_first_line = doc.loc().y == 0;
+ let out_of_range = doc.out_of_range(0, doc.loc().y).is_err();
+ if c == 0 && !on_first_line && !out_of_range {
+ // Backspace was pressed on the start of the line, move line to the top
+ self.new_row()?;
+ let mut loc = self.try_doc().unwrap().char_loc();
+ let file = self.files.get_mut(self.ptr.clone()).unwrap();
+ if !file.doc.info.read_only {
+ self.highlighter().remove_line(loc.y);
+ }
+ loc.y = loc.y.saturating_sub(1);
+ let file = self.files.get_mut(self.ptr.clone()).unwrap();
+ loc.x = file.doc.line(loc.y).unwrap().chars().count();
+ self.exe(Event::SpliceUp(loc))?;
+ let file = self.files.get_mut(self.ptr.clone()).unwrap();
+ let line = &file.doc.lines[loc.y];
+ if !file.doc.info.read_only {
+ file.highlighter.edit(loc.y, line);
+ }
+ } else if !(c == 0 && on_first_line) {
+ // Backspace was pressed in the middle of the line, delete the character
+ c = c.saturating_sub(1);
+ if let Some(line) = doc.line(doc.loc().y) {
+ if let Some(ch) = line.chars().nth(c) {
+ let loc = Loc {
+ x: c,
+ y: doc.loc().y,
+ };
+ self.exe(Event::Delete(loc, ch.to_string()))?;
+ let file = self.files.get_mut(self.ptr.clone()).unwrap();
+ if !file.doc.info.read_only {
+ file.highlighter.edit(loc.y, &file.doc.lines[loc.y]);
+ }
}
}
}
@@ -125,17 +138,19 @@ impl Editor {
/// Delete the character in place
pub fn delete(&mut self) -> Result<()> {
- let c = self.doc().char_ptr;
- if let Some(line) = self.doc().line(self.doc().loc().y) {
- if let Some(ch) = line.chars().nth(c) {
- let loc = Loc {
- x: c,
- y: self.doc().loc().y,
- };
- self.exe(Event::Delete(loc, ch.to_string()))?;
- if let Some(file) = self.files.get_mut(self.ptr.clone()) {
- if !file.doc.info.read_only {
- file.highlighter.edit(loc.y, &file.doc.lines[loc.y]);
+ if let Some(doc) = self.try_doc() {
+ let c = doc.char_ptr;
+ if let Some(line) = doc.line(doc.loc().y) {
+ if let Some(ch) = line.chars().nth(c) {
+ let loc = Loc {
+ x: c,
+ y: doc.loc().y,
+ };
+ self.exe(Event::Delete(loc, ch.to_string()))?;
+ if let Some(file) = self.files.get_mut(self.ptr.clone()) {
+ if !file.doc.info.read_only {
+ file.highlighter.edit(loc.y, &file.doc.lines[loc.y]);
+ }
}
}
}
@@ -145,10 +160,14 @@ impl Editor {
/// Insert a new row at the end of the document if the cursor is on it
fn new_row(&mut self) -> Result<()> {
- if self.doc().loc().y == self.doc().len_lines() {
- self.exe(Event::InsertLine(self.doc().loc().y, String::new()))?;
- if !self.doc().info.read_only {
- self.highlighter().append("");
+ if self.try_doc().is_some() {
+ let doc = self.try_doc().unwrap();
+ if doc.loc().y == doc.len_lines() {
+ self.exe(Event::InsertLine(doc.loc().y, String::new()))?;
+ let doc = self.try_doc().unwrap();
+ if !doc.info.read_only {
+ self.highlighter().append("");
+ }
}
}
Ok(())
@@ -156,13 +175,17 @@ impl Editor {
/// Delete the current line
pub fn delete_line(&mut self) -> Result<()> {
- // Delete the line
- if self.doc().loc().y < self.doc().len_lines() {
- let y = self.doc().loc().y;
- let line = self.doc().line(y).unwrap();
- self.exe(Event::DeleteLine(y, line))?;
- if !self.doc().info.read_only {
- self.highlighter().remove_line(y);
+ if self.try_doc().is_some() {
+ // Delete the line
+ let doc = self.try_doc().unwrap();
+ if doc.loc().y < doc.len_lines() {
+ let y = doc.loc().y;
+ let line = doc.line(y).unwrap();
+ self.exe(Event::DeleteLine(y, line))?;
+ let doc = self.try_doc().unwrap();
+ if !doc.info.read_only {
+ self.highlighter().remove_line(y);
+ }
}
}
Ok(())
@@ -170,35 +193,47 @@ impl Editor {
/// Perform redo action
pub fn redo(&mut self) -> Result<()> {
- let result = Ok(self.doc_mut().redo()?);
- self.reload_highlight();
- result
+ if let Some(doc) = self.try_doc_mut() {
+ doc.redo()?;
+ self.reload_highlight();
+ }
+ Ok(())
}
/// Perform undo action
pub fn undo(&mut self) -> Result<()> {
- let result = Ok(self.doc_mut().undo()?);
- self.reload_highlight();
- result
+ if let Some(doc) = self.try_doc_mut() {
+ doc.undo()?;
+ self.reload_highlight();
+ }
+ Ok(())
}
/// Copy the selected text
pub fn copy(&mut self) -> Result<()> {
- let selected_text = self.doc().selection_text();
- self.terminal.copy(&selected_text)
+ if let Some(doc) = self.try_doc() {
+ let selected_text = doc.selection_text();
+ self.terminal.copy(&selected_text)
+ } else {
+ Ok(())
+ }
}
/// Cut the selected text
pub fn cut(&mut self) -> Result<()> {
- self.copy()?;
- self.doc_mut().remove_selection();
- self.reload_highlight();
+ if self.try_doc().is_some() {
+ self.copy()?;
+ self.try_doc_mut().unwrap().remove_selection();
+ self.reload_highlight();
+ }
Ok(())
}
/// Shortcut to help rehighlight a line
pub fn hl_edit(&mut self, y: usize) {
- let line = self.doc().line(y).unwrap_or_default();
- self.highlighter().edit(y, &line);
+ if let Some(doc) = self.try_doc() {
+ let line = doc.line(y).unwrap_or_default();
+ self.highlighter().edit(y, &line);
+ }
}
}
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
new file mode 100644
index 00000000..f9ed7034
--- /dev/null
+++ b/src/editor/filetree.rs
@@ -0,0 +1,52 @@
+/// Utilities for handling the file tree
+
+use crate::Editor;
+use crate::editor::FileLayout;
+
+impl Editor {
+ /// Open the file tree
+ pub fn open_file_tree(&mut self) {
+ if !self.file_tree_is_open() {
+ // Wrap existing file layout in new file layout
+ self.files = FileLayout::SideBySide(
+ vec![
+ (FileLayout::FileTree, 0.2),
+ (self.files.clone(), 0.8),
+ ]
+ );
+ self.ptr = vec![0];
+ }
+ }
+
+ /// Close the file tree
+ pub fn close_file_tree(&mut self) {
+ if let Some(FileLayout::SideBySide(layouts)) = self.files.get_raw(vec![]) {
+ // Locate where the file tree is
+ let ftp = layouts.iter().position(|(l, _)| matches!(l, FileLayout::FileTree));
+ if let Some(at) = ftp {
+ // Delete the file tree
+ self.files.remove(vec![at]);
+ }
+ }
+ }
+
+ /// Toggle the file tree
+ pub fn toggle_file_tree(&mut self) {
+ if self.file_tree_is_open() {
+ self.close_file_tree();
+ } else {
+ self.open_file_tree();
+ }
+ }
+
+ /// Determine whether the file tree is open
+ pub fn file_tree_is_open(&self) -> bool {
+ if let Some(FileLayout::SideBySide(layouts)) = self.files.get_raw(vec![]) {
+ layouts
+ .iter()
+ .any(|(layout, _)| matches!(layout, FileLayout::FileTree))
+ } else {
+ false
+ }
+ }
+}
diff --git a/src/editor/interface.rs b/src/editor/interface.rs
index 3600499d..818285af 100644
--- a/src/editor/interface.rs
+++ b/src/editor/interface.rs
@@ -65,6 +65,7 @@ impl Editor {
let mut accounted_for = 0;
// Render each component of this line
for (c, (fc, rows, range)) in fcs.iter().enumerate() {
+ let in_file_tree = matches!(self.files.get_raw(fc.to_owned()), Some(FileLayout::FileTree));
// Check if we have encountered an area of discontinuity in the line
if range.start != accounted_for {
// Discontinuity detected, fill with vertical bar!
@@ -93,7 +94,10 @@ impl Editor {
let length = range.end.saturating_sub(range.start);
let height = rows.end.saturating_sub(rows.start);
let rel_y = y.saturating_sub(rows.start);
- if y == rows.start && tab_line_enabled {
+ if in_file_tree {
+ // Part of file tree!
+ result += &" ".repeat(length);
+ } else if y == rows.start && tab_line_enabled {
// Tab line
result += &self.render_tab_line(fc, lua, length)?;
} else if y == rows.end.saturating_sub(1) {
@@ -201,13 +205,15 @@ impl Editor {
/// Function to calculate the cursor's position on screen
pub fn cursor_position(&self) -> Option {
- let Loc { x, y } = self.doc().cursor_loc_in_screen()?;
- for (ptr, rows, cols) in &self.render_cache.span {
- if ptr == &self.ptr {
- return Some(Loc {
- x: cols.start + x + self.dent(),
- y: rows.start + y + self.push_down,
- });
+ if !matches!(self.files.get_raw(self.ptr.clone()), Some(FileLayout::FileTree)) {
+ let Loc { x, y } = self.try_doc().unwrap().cursor_loc_in_screen()?;
+ for (ptr, rows, cols) in &self.render_cache.span {
+ if ptr == &self.ptr {
+ return Some(Loc {
+ x: cols.start + x + self.dent(),
+ y: rows.start + y + self.push_down,
+ });
+ }
}
}
None
diff --git a/src/editor/mod.rs b/src/editor/mod.rs
index 076c6456..46e630ba 100644
--- a/src/editor/mod.rs
+++ b/src/editor/mod.rs
@@ -19,6 +19,7 @@ use synoptic::Highlighter;
mod cursor;
mod documents;
mod editing;
+mod filetree;
mod filetypes;
mod interface;
mod macros;
@@ -252,45 +253,49 @@ impl Editor {
/// save the document to the disk
pub fn save(&mut self) -> Result<()> {
- // Perform the save
- self.doc_mut().save()?;
- // All done
- self.feedback = Feedback::Info("Document saved successfully".to_string());
+ if let Some(doc) = self.try_doc_mut() {
+ // Perform the save
+ doc.save()?;
+ // All done
+ self.feedback = Feedback::Info("Document saved successfully".to_string());
+ }
Ok(())
}
/// save the document to the disk at a specified path
pub fn save_as(&mut self) -> Result<()> {
- let file_name = self.prompt("Save as")?;
- self.doc_mut().save_as(&file_name)?;
- if self.doc().file_name.is_none() {
- if let Some((files, _)) = self.files.get_atom_mut(self.ptr.clone()) {
+ if self.try_doc().is_some() {
+ let file_name = self.prompt("Save as")?;
+ self.try_doc_mut().unwrap().save_as(&file_name)?;
+ if self.try_doc().unwrap().file_name.is_none() {
let tab_width = config!(self.config, document).tab_width;
- let file = files.last_mut().unwrap();
- // Set the file name
- file.doc.file_name = Some(file_name.clone());
- // Update the file type
- file.file_type = config!(self.config, document)
- .file_types
- .identify(&mut file.doc);
- // Reattach an appropriate highlighter
- let highlighter = file
- .file_type
- .clone()
- .map_or(Highlighter::new(tab_width), |t| {
- t.get_highlighter(&self.config, tab_width)
- });
- file.highlighter = highlighter;
- file.highlighter.run(&file.doc.lines);
- // Set up to date with disk
- file.doc.event_mgmt.force_not_with_disk = false;
- file.doc.event_mgmt.disk_write(&file.doc.take_snapshot());
+ if let Some((files, _)) = self.files.get_atom_mut(self.ptr.clone()) {
+ let file = files.last_mut().unwrap();
+ // Set the file name
+ file.doc.file_name = Some(file_name.clone());
+ // Update the file type
+ file.file_type = config!(self.config, document)
+ .file_types
+ .identify(&mut file.doc);
+ // Reattach an appropriate highlighter
+ let highlighter = file
+ .file_type
+ .clone()
+ .map_or(Highlighter::new(tab_width), |t| {
+ t.get_highlighter(&self.config, tab_width)
+ });
+ file.highlighter = highlighter;
+ file.highlighter.run(&file.doc.lines);
+ // Set up to date with disk
+ file.doc.event_mgmt.force_not_with_disk = false;
+ file.doc.event_mgmt.disk_write(&file.doc.take_snapshot());
+ }
}
+ // Commit events to event manager (for undo / redo)
+ self.try_doc_mut().unwrap().commit();
+ // All done
+ self.feedback = Feedback::Info(format!("Document saved as {file_name} successfully"));
}
- // Commit events to event manager (for undo / redo)
- self.doc_mut().commit();
- // All done
- self.feedback = Feedback::Info(format!("Document saved as {file_name} successfully"));
Ok(())
}
@@ -351,10 +356,12 @@ impl Editor {
/// Updates the current working directory of the editor
pub fn update_cwd(&self) {
- if let Some(name) = get_absolute_path(&self.doc().file_name.clone().unwrap_or_default()) {
- let file = Path::new(&name);
- if let Some(cwd) = file.parent() {
- let _ = env::set_current_dir(cwd);
+ if let Some(doc) = self.try_doc() {
+ if let Some(name) = get_absolute_path(&doc.file_name.clone().unwrap_or_default()) {
+ let file = Path::new(&name);
+ if let Some(cwd) = file.parent() {
+ let _ = env::set_current_dir(cwd);
+ }
}
}
}
@@ -376,16 +383,6 @@ impl Editor {
&mut self.files.get_atom_mut(self.ptr.clone()).unwrap().0[idx].doc
}
- /// Gets a reference to the current document
- pub fn doc(&self) -> &Document {
- &self.files.get(self.ptr.clone()).unwrap().doc
- }
-
- /// Gets a mutable reference to the current document
- pub fn doc_mut(&mut self) -> &mut Document {
- &mut self.files.get_mut(self.ptr.clone()).unwrap().doc
- }
-
/// Gets the number of documents currently open
pub fn doc_len(&mut self) -> usize {
self.files.get_atom(self.ptr.clone()).unwrap().0.len()
@@ -435,24 +432,26 @@ impl Editor {
/// Handle key event
pub fn handle_key_event(&mut self, modifiers: KMod, code: KCode) -> Result<()> {
- // Check period of inactivity
- let end = Instant::now();
- let inactivity = end.duration_since(self.last_active).as_millis() as usize;
- // Commit if over user-defined period of inactivity
- if inactivity > config!(self.config, document).undo_period * 1000 {
- self.doc_mut().commit();
- }
- // Register this activity
- self.last_active = Instant::now();
- // Editing - these key bindings can't be modified (only added to)!
- match (modifiers, code) {
- // Core key bindings (non-configurable behaviour)
- (KMod::SHIFT | KMod::NONE, KCode::Char(ch)) => self.character(ch)?,
- (KMod::NONE, KCode::Tab) => self.handle_tab()?,
- (KMod::NONE, KCode::Backspace) => self.backspace()?,
- (KMod::NONE, KCode::Delete) => self.delete()?,
- (KMod::NONE, KCode::Enter) => self.enter()?,
- _ => (),
+ if self.try_doc().is_some() {
+ // Check period of inactivity
+ let end = Instant::now();
+ let inactivity = end.duration_since(self.last_active).as_millis() as usize;
+ // Commit if over user-defined period of inactivity
+ if inactivity > config!(self.config, document).undo_period * 1000 {
+ self.try_doc_mut().unwrap().commit();
+ }
+ // Register this activity
+ self.last_active = Instant::now();
+ // Editing - these key bindings can't be modified (only added to)!
+ match (modifiers, code) {
+ // Core key bindings (non-configurable behaviour)
+ (KMod::SHIFT | KMod::NONE, KCode::Char(ch)) => self.character(ch)?,
+ (KMod::NONE, KCode::Tab) => self.handle_tab()?,
+ (KMod::NONE, KCode::Backspace) => self.backspace()?,
+ (KMod::NONE, KCode::Delete) => self.delete()?,
+ (KMod::NONE, KCode::Enter) => self.enter()?,
+ _ => (),
+ }
}
Ok(())
}
@@ -466,23 +465,25 @@ impl Editor {
/// Handle paste
pub fn handle_paste(&mut self, text: &str) -> Result<()> {
- // If we're playing back a macro, use the last text the user copied
- // (to prevent hard-coded pasting)
- let text = if self.macro_man.playing {
- self.terminal.last_copy.to_string()
- } else {
- text.to_string()
- };
- // Save state before paste
- self.doc_mut().commit();
- // Apply paste
- self.pasting = true;
- for ch in text.chars() {
- self.character(ch)?;
+ if self.try_doc().is_some() {
+ // If we're playing back a macro, use the last text the user copied
+ // (to prevent hard-coded pasting)
+ let text = if self.macro_man.playing {
+ self.terminal.last_copy.to_string()
+ } else {
+ text.to_string()
+ };
+ // Save state before paste
+ self.try_doc_mut().unwrap().commit();
+ // Apply paste
+ self.pasting = true;
+ for ch in text.chars() {
+ self.character(ch)?;
+ }
+ self.pasting = false;
+ // Save state after paste
+ self.try_doc_mut().unwrap().commit();
}
- self.pasting = false;
- // Save state after paste
- self.doc_mut().commit();
Ok(())
}
diff --git a/src/editor/mouse.rs b/src/editor/mouse.rs
index 16041df5..e1800910 100644
--- a/src/editor/mouse.rs
+++ b/src/editor/mouse.rs
@@ -34,48 +34,52 @@ impl Editor {
if let Some((idx, rows, cols)) = at_idx {
let idx = idx.clone();
// Calculate the current dent in this split
- let doc_idx = self.files.get_atom(idx.clone()).unwrap().1;
- let dent = self.dent_for(&idx, doc_idx);
- // Split that user clicked in located - adjust event location
- let clicked = Loc {
- x: col.saturating_sub(cols.start),
- y: row.saturating_sub(rows.start),
- };
- // Work out where the user clicked
- if clicked.y == 0 && tab_enabled {
- // Clicked on tab line
- let (tabs, _, offset) =
- self.get_tab_parts(&idx, lua, cols.end.saturating_sub(cols.start));
- // Try to work out which tab we clicked on
- let mut c = u16::try_from(clicked.x).unwrap_or(u16::MAX) + 2;
- for (i, header) in tabs.iter().enumerate() {
- let header_len = width(header, 4) + 1;
- c = c.saturating_sub(u16::try_from(header_len).unwrap_or(u16::MAX));
- if c == 0 {
- // This tab was clicked on
- return MouseLocation::Tabs(idx.clone(), i + offset);
+ if let Some((_, doc_idx)) = self.files.get_atom(idx.clone()) {
+ let dent = self.dent_for(&idx, doc_idx);
+ // Split that user clicked in located - adjust event location
+ let clicked = Loc {
+ x: col.saturating_sub(cols.start),
+ y: row.saturating_sub(rows.start),
+ };
+ // Work out where the user clicked
+ if clicked.y == 0 && tab_enabled {
+ // Clicked on tab line
+ let (tabs, _, offset) =
+ self.get_tab_parts(&idx, lua, cols.end.saturating_sub(cols.start));
+ // Try to work out which tab we clicked on
+ let mut c = u16::try_from(clicked.x).unwrap_or(u16::MAX) + 2;
+ for (i, header) in tabs.iter().enumerate() {
+ let header_len = width(header, 4) + 1;
+ c = c.saturating_sub(u16::try_from(header_len).unwrap_or(u16::MAX));
+ if c == 0 {
+ // This tab was clicked on
+ return MouseLocation::Tabs(idx.clone(), i + offset);
+ }
}
+ // Did not click on a tab
+ MouseLocation::Out
+ } else if clicked.y == rows.end.saturating_sub(1) {
+ // Clicked on status line
+ MouseLocation::Out
+ } else if clicked.x < dent {
+ // Clicked on line numbers
+ MouseLocation::Out
+ } else if let Some((fcs, ptr)) = self.files.get_atom(idx.clone()) {
+ // Clicked on document
+ let offset = fcs[ptr].doc.offset;
+ MouseLocation::File(
+ idx.clone(),
+ Loc {
+ x: clicked.x.saturating_sub(dent) + offset.x,
+ y: clicked.y.saturating_sub(tab) + offset.y,
+ },
+ )
+ } else {
+ // We can't seem to get the atom for some reason, just default to Out
+ MouseLocation::Out
}
- // Did not click on a tab
- MouseLocation::Out
- } else if clicked.y == rows.end.saturating_sub(1) {
- // Clicked on status line
- MouseLocation::Out
- } else if clicked.x < dent {
- // Clicked on line numbers
- MouseLocation::Out
- } else if let Some((fcs, ptr)) = self.files.get_atom(idx.clone()) {
- // Clicked on document
- let offset = fcs[ptr].doc.offset;
- MouseLocation::File(
- idx.clone(),
- Loc {
- x: clicked.x.saturating_sub(dent) + offset.x,
- y: clicked.y.saturating_sub(tab) + offset.y,
- },
- )
} else {
- // We can't seem to get the atom for some reason, just default to Out
+ // Pretty sure we just clicked on the file tree (split with no atom!)
MouseLocation::Out
}
} else {
@@ -104,10 +108,12 @@ impl Editor {
match self.find_mouse_location(lua, event) {
MouseLocation::File(idx, mut loc) => {
self.ptr.clone_from(&idx);
- self.doc_mut().clear_cursors();
- loc.x = self.doc_mut().character_idx(&loc);
- self.doc_mut().move_to(&loc);
- self.doc_mut().old_cursor = self.doc().loc().x;
+ if let Some(doc) = self.try_doc_mut() {
+ doc.clear_cursors();
+ loc.x = doc.character_idx(&loc);
+ doc.move_to(&loc);
+ doc.old_cursor = doc.loc().x;
+ }
}
MouseLocation::Tabs(idx, i) => {
self.files.move_to(idx.clone(), i);
@@ -121,18 +127,20 @@ impl Editor {
// Select the current line
if let MouseLocation::File(idx, loc) = self.find_mouse_location(lua, event) {
self.ptr.clone_from(&idx);
- self.doc_mut().select_line_at(loc.y);
- let line = self.doc().line(loc.y).unwrap_or_default();
- self.alt_click_state = Some((
- Loc {
- x: 0,
- y: self.doc().loc().y,
- },
- Loc {
- x: line.chars().count(),
- y: self.doc().loc().y,
- },
- ));
+ if let Some(doc) = self.try_doc_mut() {
+ doc.select_line_at(loc.y);
+ let line = doc.line(loc.y).unwrap_or_default();
+ self.alt_click_state = Some((
+ Loc {
+ x: 0,
+ y: doc.loc().y,
+ },
+ Loc {
+ x: line.chars().count(),
+ y: doc.loc().y,
+ },
+ ));
+ }
}
}
MouseEventKind::Up(MouseButton::Right) => {
@@ -149,22 +157,29 @@ impl Editor {
MouseEventKind::Drag(MouseButton::Left) => {
match self.find_mouse_location(lua, event) {
MouseLocation::File(idx, mut loc) => {
- self.ptr.clone_from(&idx);
- loc.x = self.doc_mut().character_idx(&loc);
- if let Some((dbl_start, dbl_end)) = self.alt_click_state {
- if loc.x > self.doc().cursor.selection_end.x {
- // Find boundary of next word
- let next = self.doc().next_word_close(loc);
- self.doc_mut().move_to(&dbl_start);
- self.doc_mut().select_to(&Loc { x: next, y: loc.y });
+ if self.try_doc().is_some() {
+ self.ptr.clone_from(&idx);
+ let doc = self.try_doc().unwrap();
+ loc.x = doc.character_idx(&loc);
+ if let Some((dbl_start, dbl_end)) = self.alt_click_state {
+ let doc = self.try_doc().unwrap();
+ if loc.x > doc.cursor.selection_end.x {
+ // Find boundary of next word
+ let next = doc.next_word_close(loc);
+ let doc = self.try_doc_mut().unwrap();
+ doc.move_to(&dbl_start);
+ doc.select_to(&Loc { x: next, y: loc.y });
+ } else {
+ // Find boundary of previous word
+ let next = doc.prev_word_close(loc);
+ let doc = self.try_doc_mut().unwrap();
+ doc.move_to(&dbl_end);
+ doc.select_to(&Loc { x: next, y: loc.y });
+ }
} else {
- // Find boundary of previous word
- let next = self.doc().prev_word_close(loc);
- self.doc_mut().move_to(&dbl_end);
- self.doc_mut().select_to(&Loc { x: next, y: loc.y });
+ let doc = self.try_doc_mut().unwrap();
+ doc.select_to(&loc);
}
- } else {
- self.doc_mut().select_to(&loc);
}
}
MouseLocation::Tabs(_, _) | MouseLocation::Out => (),
@@ -173,22 +188,28 @@ impl Editor {
MouseEventKind::Drag(MouseButton::Right) => {
match self.find_mouse_location(lua, event) {
MouseLocation::File(idx, mut loc) => {
- self.ptr.clone_from(&idx);
- loc.x = self.doc_mut().character_idx(&loc);
- if let Some((line_start, line_end)) = self.alt_click_state {
- if loc.y > self.doc().cursor.selection_end.y {
- let line = self.doc().line(loc.y).unwrap_or_default();
- self.doc_mut().move_to(&line_start);
- self.doc_mut().select_to(&Loc {
- x: line.chars().count(),
- y: loc.y,
- });
+ if self.try_doc().is_some() {
+ self.ptr.clone_from(&idx);
+ let doc = self.try_doc_mut().unwrap();
+ loc.x = doc.character_idx(&loc);
+ if let Some((line_start, line_end)) = self.alt_click_state {
+ let doc = self.try_doc().unwrap();
+ if loc.y > doc.cursor.selection_end.y {
+ let line = doc.line(loc.y).unwrap_or_default();
+ let doc = self.try_doc_mut().unwrap();
+ doc.move_to(&line_start);
+ doc.select_to(&Loc {
+ x: line.chars().count(),
+ y: loc.y,
+ });
+ } else {
+ let doc = self.try_doc_mut().unwrap();
+ doc.move_to(&line_end);
+ doc.select_to(&Loc { x: 0, y: loc.y });
+ }
} else {
- self.doc_mut().move_to(&line_end);
- self.doc_mut().select_to(&Loc { x: 0, y: loc.y });
+ self.try_doc_mut().unwrap().select_to(&loc);
}
- } else {
- self.doc_mut().select_to(&loc);
}
}
MouseLocation::Tabs(_, _) | MouseLocation::Out => (),
@@ -196,23 +217,29 @@ impl Editor {
}
// Mouse scroll behaviour
MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => {
+ let scroll_amount = config!(self.config, terminal).scroll_amount;
if let MouseLocation::File(idx, _) = self.find_mouse_location(lua, event) {
self.ptr.clone_from(&idx);
- let scroll_amount = config!(self.config, terminal).scroll_amount;
- for _ in 0..scroll_amount {
- if event.kind == MouseEventKind::ScrollDown {
- self.doc_mut().scroll_down();
- } else {
- self.doc_mut().scroll_up();
+ if let Some(doc) = self.try_doc_mut() {
+ for _ in 0..scroll_amount {
+ if event.kind == MouseEventKind::ScrollDown {
+ doc.scroll_down();
+ } else {
+ doc.scroll_up();
+ }
}
}
}
}
MouseEventKind::ScrollLeft => {
- self.doc_mut().move_left();
+ if let Some(doc) = self.try_doc_mut() {
+ doc.move_left();
+ }
}
MouseEventKind::ScrollRight => {
- self.doc_mut().move_right();
+ if let Some(doc) = self.try_doc_mut() {
+ doc.move_right();
+ }
}
_ => (),
},
@@ -221,8 +248,10 @@ impl Editor {
if let MouseEventKind::Down(MouseButton::Left) = event.kind {
if let MouseLocation::File(idx, loc) = self.find_mouse_location(lua, event) {
self.ptr.clone_from(&idx);
- self.doc_mut().new_cursor(loc);
- self.doc_mut().commit();
+ if let Some(doc) = self.try_doc_mut() {
+ doc.new_cursor(loc);
+ doc.commit();
+ }
}
}
}
@@ -235,12 +264,14 @@ impl Editor {
// Select the current word
if let MouseLocation::File(idx, loc) = self.find_mouse_location(lua, event) {
self.ptr.clone_from(&idx);
- self.doc_mut().select_word_at(&loc);
- let mut selection = self.doc().cursor.selection_end;
- let mut cursor = self.doc().cursor.loc;
- selection.x = self.doc().character_idx(&selection);
- cursor.x = self.doc().character_idx(&cursor);
- self.alt_click_state = Some((selection, cursor));
+ if let Some(doc) = self.try_doc_mut() {
+ doc.select_word_at(&loc);
+ let mut selection = doc.cursor.selection_end;
+ let mut cursor = doc.cursor.loc;
+ selection.x = doc.character_idx(&selection);
+ cursor.x = doc.character_idx(&cursor);
+ self.alt_click_state = Some((selection, cursor));
+ }
}
}
}
diff --git a/src/editor/scanning.rs b/src/editor/scanning.rs
index 09c9716e..02355f2d 100644
--- a/src/editor/scanning.rs
+++ b/src/editor/scanning.rs
@@ -15,8 +15,13 @@ use super::Editor;
impl Editor {
/// Use search feature
pub fn search(&mut self, lua: &Lua) -> Result<()> {
+ // Block any non-documents from activating search
+ if self.try_doc().is_none() {
+ return Ok(());
+ }
+ // Gather data
let editor_bg = Bg(config!(self.config, colors).editor_bg.to_color()?);
- let cache = self.doc().char_loc();
+ let cache = self.try_doc().unwrap().char_loc();
// Prompt for a search term
let mut target = String::new();
let mut done = false;
@@ -51,20 +56,20 @@ impl Editor {
(KMod::NONE, KCode::Enter) => done = true,
// Cancel operation
(KMod::NONE, KCode::Esc) => {
- self.doc_mut().move_to(&cache);
- self.doc_mut().cancel_selection();
+ self.try_doc_mut().unwrap().move_to(&cache);
+ self.try_doc_mut().unwrap().cancel_selection();
return Err(OxError::Cancelled);
}
// Remove from the input string if the user presses backspace
(KMod::NONE, KCode::Backspace) => {
target.pop();
- self.doc_mut().move_to(&cache);
+ self.try_doc_mut().unwrap().move_to(&cache);
self.next_match(&target);
}
// Add to the input string if the user presses a character
(KMod::NONE | KMod::SHIFT, KCode::Char(c)) => {
target.push(c);
- self.doc_mut().move_to(&cache);
+ self.try_doc_mut().unwrap().move_to(&cache);
self.next_match(&target);
}
_ => (),
@@ -104,7 +109,7 @@ impl Editor {
// On return or escape key, exit menu
(KMod::NONE, KCode::Enter) => done = true,
(KMod::NONE, KCode::Esc) => {
- self.doc_mut().move_to(&cache);
+ self.try_doc_mut().unwrap().move_to(&cache);
done = true;
}
// On left key, move to the previous match in the document
@@ -116,41 +121,54 @@ impl Editor {
}
self.update_highlighter();
}
- self.doc_mut().cancel_selection();
+ self.try_doc_mut().unwrap().cancel_selection();
Ok(())
}
/// Move to the next match
pub fn next_match(&mut self, target: &str) -> Option {
- let mtch = self.doc_mut().next_match(target, 1)?;
- // Select match
- self.doc_mut().cancel_selection();
- let mut move_to = mtch.loc;
- move_to.x += mtch.text.chars().count();
- self.doc_mut().move_to(&move_to);
- self.doc_mut().select_to(&mtch.loc);
- // Update highlighting
- self.update_highlighter();
- Some(mtch.text)
+ if let Some(doc) = self.try_doc_mut() {
+ let mtch = doc.next_match(target, 1)?;
+ // Select match
+ doc.cancel_selection();
+ let mut move_to = mtch.loc;
+ move_to.x += mtch.text.chars().count();
+ doc.move_to(&move_to);
+ doc.select_to(&mtch.loc);
+ // Update highlighting
+ self.update_highlighter();
+ Some(mtch.text)
+ } else {
+ None
+ }
}
/// Move to the previous match
pub fn prev_match(&mut self, target: &str) -> Option {
- let mtch = self.doc_mut().prev_match(target)?;
- self.doc_mut().move_to(&mtch.loc);
- // Select match
- self.doc_mut().cancel_selection();
- let mut move_to = mtch.loc;
- move_to.x += mtch.text.chars().count();
- self.doc_mut().move_to(&move_to);
- self.doc_mut().select_to(&mtch.loc);
- // Update highlighting
- self.update_highlighter();
- Some(mtch.text)
+ if let Some(doc) = self.try_doc_mut() {
+ let mtch = doc.prev_match(target)?;
+ doc.move_to(&mtch.loc);
+ // Select match
+ doc.cancel_selection();
+ let mut move_to = mtch.loc;
+ move_to.x += mtch.text.chars().count();
+ doc.move_to(&move_to);
+ doc.select_to(&mtch.loc);
+ // Update highlighting
+ self.update_highlighter();
+ Some(mtch.text)
+ } else {
+ None
+ }
}
/// Use replace feature
pub fn replace(&mut self, lua: &Lua) -> Result<()> {
+ // Block any non-documents from activating replace
+ if self.try_doc().is_none() {
+ return Ok(());
+ }
+ // Gather data
let editor_bg = Bg(config!(self.config, colors).editor_bg.to_color()?);
// Request replace information
let target = self.prompt("Replace")?;
@@ -215,38 +233,42 @@ impl Editor {
// Update syntax highlighter if necessary
self.update_highlighter();
}
- self.doc_mut().cancel_selection();
+ self.try_doc_mut().unwrap().cancel_selection();
Ok(())
}
/// Replace an instance in a document
fn do_replace(&mut self, into: &str, text: &str) -> Result<()> {
- // Commit events to event manager (for undo / redo)
- self.doc_mut().commit();
- // Do the replacement
- let loc = self.doc().char_loc();
- self.doc_mut().replace(loc, text, into)?;
- self.doc_mut().move_to(&loc);
- // Update syntax highlighter
- self.update_highlighter();
- if let Some(file) = self.files.get_mut(self.ptr.clone()) {
- file.highlighter.edit(loc.y, &file.doc.lines[loc.y]);
+ if let Some(doc) = self.try_doc_mut() {
+ // Commit events to event manager (for undo / redo)
+ doc.commit();
+ // Do the replacement
+ let loc = doc.char_loc();
+ doc.replace(loc, text, into)?;
+ doc.move_to(&loc);
+ // Update syntax highlighter
+ self.update_highlighter();
+ if let Some(file) = self.files.get_mut(self.ptr.clone()) {
+ file.highlighter.edit(loc.y, &file.doc.lines[loc.y]);
+ }
}
Ok(())
}
/// Replace all instances in a document
fn do_replace_all(&mut self, target: &str, into: &str) {
- // Commit events to event manager (for undo / redo)
- self.doc_mut().commit();
- // Replace everything top to bottom
- self.doc_mut().move_to(&Loc::at(0, 0));
- while let Some(mtch) = self.doc_mut().next_match(target, 1) {
- drop(self.doc_mut().replace(mtch.loc, &mtch.text, into));
- self.update_highlighter();
- if let Some(file) = self.files.get_mut(self.ptr.clone()) {
- file.highlighter
- .edit(mtch.loc.y, &file.doc.lines[mtch.loc.y]);
+ if self.try_doc().is_some() {
+ // Commit events to event manager (for undo / redo)
+ self.try_doc_mut().unwrap().commit();
+ // Replace everything top to bottom
+ self.try_doc_mut().unwrap().move_to(&Loc::at(0, 0));
+ while let Some(mtch) = self.try_doc_mut().unwrap().next_match(target, 1) {
+ drop(self.try_doc_mut().unwrap().replace(mtch.loc, &mtch.text, into));
+ self.update_highlighter();
+ if let Some(file) = self.files.get_mut(self.ptr.clone()) {
+ file.highlighter
+ .edit(mtch.loc.y, &file.doc.lines[mtch.loc.y]);
+ }
}
}
}
diff --git a/src/main.rs b/src/main.rs
index 8a5728cd..59614a59 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -19,7 +19,7 @@ use events::wait_for_event;
use kaolinite::event::{Error as KError, Event};
use kaolinite::searching::Searcher;
use kaolinite::utils::{file_or_dir, get_cwd};
-use kaolinite::Loc;
+use kaolinite::{Document, Loc};
use mlua::Error::{RuntimeError, SyntaxError};
use mlua::{AnyUserData, FromLua, Lua, Value};
use std::io::ErrorKind;
@@ -184,7 +184,7 @@ fn run(cli: &CommandLineInterface) -> Result<()> {
let event = wait_for_event(&editor, &lua)?;
// Handle the event
- let original_loc = ged!(&editor).doc().char_loc();
+ let original_loc = ged!(&editor).try_doc().map(Document::char_loc).unwrap_or_default();
handle_event(&editor, &event, &lua)?;
// Handle multi cursors
From def1fc7dd832d22ab4cf5a1259d1b002e8bdb1d7 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Mon, 2 Dec 2024 00:35:00 +0000
Subject: [PATCH 03/51] rustfmt
---
src/editor/cursor.rs | 5 ++++-
src/editor/documents.rs | 4 +++-
src/editor/editing.rs | 3 ++-
src/editor/filetree.rs | 17 ++++++++---------
src/editor/interface.rs | 11 +++++++++--
src/editor/scanning.rs | 6 +++++-
src/main.rs | 5 ++++-
7 files changed, 35 insertions(+), 16 deletions(-)
diff --git a/src/editor/cursor.rs b/src/editor/cursor.rs
index 516375bd..e742b4a9 100644
--- a/src/editor/cursor.rs
+++ b/src/editor/cursor.rs
@@ -148,7 +148,10 @@ pub fn handle_multiple_cursors(
while ptr < secondary_cursors.len() {
// Move to the secondary cursor position
let sec_cursor = secondary_cursors[ptr];
- ged!(mut &editor).try_doc_mut().unwrap().move_to(&sec_cursor);
+ ged!(mut &editor)
+ .try_doc_mut()
+ .unwrap()
+ .move_to(&sec_cursor);
// Replay the event
let old_loc = ged!(&editor).try_doc().unwrap().char_loc();
handle_event(editor, event, lua)?;
diff --git a/src/editor/documents.rs b/src/editor/documents.rs
index b9640a08..ee74a33e 100644
--- a/src/editor/documents.rs
+++ b/src/editor/documents.rs
@@ -41,7 +41,9 @@ impl FileLayout {
match self {
Self::None => vec![],
// Atom and file trees: stretch from starting position through to end of their containers
- Self::Atom(_, _) | Self::FileTree => vec![(idx, at.y..at.y + size.h, at.x..at.x + size.w)],
+ Self::Atom(_, _) | Self::FileTree => {
+ vec![(idx, at.y..at.y + size.h, at.x..at.x + size.w)]
+ }
// SideBySide: distributes available container space to each sub-layout
Self::SideBySide(layouts) => {
let mut result = vec![];
diff --git a/src/editor/editing.rs b/src/editor/editing.rs
index d49e2dc5..3391b741 100644
--- a/src/editor/editing.rs
+++ b/src/editor/editing.rs
@@ -15,7 +15,8 @@ impl Editor {
// If last event is present and the same as this one, commit
let event_type_differs = last_ev.map(|e1| e1.same_type(&ev)) != Some(true);
// If last event is present and on a different line from the previous, commit
- let event_on_different_line = last_ev.map(|e| e.loc().y == ev.loc().y) != Some(true);
+ let event_on_different_line =
+ last_ev.map(|e| e.loc().y == ev.loc().y) != Some(true);
// Commit if necessary
if event_type_differs || event_on_different_line {
self.try_doc_mut().unwrap().commit();
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index f9ed7034..ab5d0b70 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -1,19 +1,16 @@
+use crate::editor::FileLayout;
/// Utilities for handling the file tree
-
use crate::Editor;
-use crate::editor::FileLayout;
impl Editor {
/// Open the file tree
pub fn open_file_tree(&mut self) {
if !self.file_tree_is_open() {
// Wrap existing file layout in new file layout
- self.files = FileLayout::SideBySide(
- vec![
- (FileLayout::FileTree, 0.2),
- (self.files.clone(), 0.8),
- ]
- );
+ self.files = FileLayout::SideBySide(vec![
+ (FileLayout::FileTree, 0.2),
+ (self.files.clone(), 0.8),
+ ]);
self.ptr = vec![0];
}
}
@@ -22,7 +19,9 @@ impl Editor {
pub fn close_file_tree(&mut self) {
if let Some(FileLayout::SideBySide(layouts)) = self.files.get_raw(vec![]) {
// Locate where the file tree is
- let ftp = layouts.iter().position(|(l, _)| matches!(l, FileLayout::FileTree));
+ let ftp = layouts
+ .iter()
+ .position(|(l, _)| matches!(l, FileLayout::FileTree));
if let Some(at) = ftp {
// Delete the file tree
self.files.remove(vec![at]);
diff --git a/src/editor/interface.rs b/src/editor/interface.rs
index 818285af..c33f04af 100644
--- a/src/editor/interface.rs
+++ b/src/editor/interface.rs
@@ -65,7 +65,10 @@ impl Editor {
let mut accounted_for = 0;
// Render each component of this line
for (c, (fc, rows, range)) in fcs.iter().enumerate() {
- let in_file_tree = matches!(self.files.get_raw(fc.to_owned()), Some(FileLayout::FileTree));
+ let in_file_tree = matches!(
+ self.files.get_raw(fc.to_owned()),
+ Some(FileLayout::FileTree)
+ );
// Check if we have encountered an area of discontinuity in the line
if range.start != accounted_for {
// Discontinuity detected, fill with vertical bar!
@@ -205,7 +208,11 @@ impl Editor {
/// Function to calculate the cursor's position on screen
pub fn cursor_position(&self) -> Option {
- if !matches!(self.files.get_raw(self.ptr.clone()), Some(FileLayout::FileTree)) {
+ let in_file_tree = matches!(
+ self.files.get_raw(self.ptr.clone()),
+ Some(FileLayout::FileTree)
+ );
+ if !in_file_tree {
let Loc { x, y } = self.try_doc().unwrap().cursor_loc_in_screen()?;
for (ptr, rows, cols) in &self.render_cache.span {
if ptr == &self.ptr {
diff --git a/src/editor/scanning.rs b/src/editor/scanning.rs
index 02355f2d..0e6b455a 100644
--- a/src/editor/scanning.rs
+++ b/src/editor/scanning.rs
@@ -263,7 +263,11 @@ impl Editor {
// Replace everything top to bottom
self.try_doc_mut().unwrap().move_to(&Loc::at(0, 0));
while let Some(mtch) = self.try_doc_mut().unwrap().next_match(target, 1) {
- drop(self.try_doc_mut().unwrap().replace(mtch.loc, &mtch.text, into));
+ drop(
+ self.try_doc_mut()
+ .unwrap()
+ .replace(mtch.loc, &mtch.text, into),
+ );
self.update_highlighter();
if let Some(file) = self.files.get_mut(self.ptr.clone()) {
file.highlighter
diff --git a/src/main.rs b/src/main.rs
index 59614a59..c397ff55 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -184,7 +184,10 @@ fn run(cli: &CommandLineInterface) -> Result<()> {
let event = wait_for_event(&editor, &lua)?;
// Handle the event
- let original_loc = ged!(&editor).try_doc().map(Document::char_loc).unwrap_or_default();
+ let original_loc = ged!(&editor)
+ .try_doc()
+ .map(Document::char_loc)
+ .unwrap_or_default();
handle_event(&editor, &event, &lua)?;
// Handle multi cursors
From badf50e3a3e5e104741beff5f7bced5a3683015e Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Mon, 2 Dec 2024 13:22:36 +0000
Subject: [PATCH 04/51] Gave file tree a background and prevented some plug-in
errors
---
plugins/autoindent.lua | 15 ++++++++++-----
plugins/pairs.lua | 6 +++++-
src/editor/interface.rs | 8 +++++++-
3 files changed, 22 insertions(+), 7 deletions(-)
diff --git a/plugins/autoindent.lua b/plugins/autoindent.lua
index 62416de8..8c709407 100644
--- a/plugins/autoindent.lua
+++ b/plugins/autoindent.lua
@@ -1,5 +1,5 @@
--[[
-Auto Indent v0.12
+Auto Indent v0.13
Helps you when programming by guessing where indentation should go
and then automatically applying these guesses as you program
@@ -119,6 +119,7 @@ end
-- Get how indented a line is at a certain y index
function autoindent:get_indent(y)
+ if y == nil then return nil end
local line = editor:get_line_at(y)
return #(line:match("^\t+") or "") + #(line:match("^ +") or "") / document.tab_width
end
@@ -181,14 +182,18 @@ for i = 32, 126 do
if char ~= "*" then
-- Keep track of whether the line was previously dedenting beforehand
event_mapping["before:" .. char] = function()
- was_dedenting = autoindent:causes_dedent(editor.cursor.y)
+ if editor.cursor ~= nil then
+ was_dedenting = autoindent:causes_dedent(editor.cursor.y)
+ end
end
-- Trigger dedent checking
event_mapping[char] = function()
-- Dedent where appropriate
- if autoindent:causes_dedent(editor.cursor.y) and not was_dedenting then
- local new_level = autoindent:get_indent(editor.cursor.y) - 1
- autoindent:set_indent(editor.cursor.y, new_level)
+ if editor.cursor ~= nil then
+ if autoindent:causes_dedent(editor.cursor.y) and not was_dedenting then
+ local new_level = autoindent:get_indent(editor.cursor.y) - 1
+ autoindent:set_indent(editor.cursor.y, new_level)
+ end
end
end
end
diff --git a/plugins/pairs.lua b/plugins/pairs.lua
index 499d9eee..3d5820e3 100644
--- a/plugins/pairs.lua
+++ b/plugins/pairs.lua
@@ -1,5 +1,5 @@
--[[
-Bracket Pairs v0.6
+Bracket Pairs v0.7
Automatically insert and delete brackets and quotes where appropriate
Also helps when you want to pad out brackets and quotes with whitespace
@@ -19,6 +19,7 @@ autopairs.just_paired = { x = nil, y = nil }
-- Determine whether we are currently inside a pair
function autopairs:in_pair()
+ if editor.cursor == nil then return false end
-- Get first candidate for a pair
local first
if editor.cursor.x == 0 then
@@ -61,6 +62,7 @@ for i, str in ipairs(autopairs.pairings) do
if start_pair == end_pair then
-- Handle hybrid start_pair and end_pair
event_mapping[start_pair] = function()
+ if editor.cursor == nil then return end
-- Check if there is a matching start pair
local at_char = ' '
if editor.cursor.x > 1 then
@@ -85,6 +87,7 @@ for i, str in ipairs(autopairs.pairings) do
else
-- Handle traditional pairs
event_mapping[end_pair] = function()
+ if editor.cursor == nil then return end
-- Check if there is a matching start pair
local at_char = editor:get_character_at(editor.cursor.x - 2, editor.cursor.y)
local potential_dupe = at_char == start_pair
@@ -100,6 +103,7 @@ for i, str in ipairs(autopairs.pairings) do
end
end
event_mapping[start_pair] = function()
+ if editor.cursor == nil then return end
autopairs.just_paired = editor.cursor
editor:insert(end_pair)
editor:move_left()
diff --git a/src/editor/interface.rs b/src/editor/interface.rs
index c33f04af..cccc26aa 100644
--- a/src/editor/interface.rs
+++ b/src/editor/interface.rs
@@ -99,7 +99,7 @@ impl Editor {
let rel_y = y.saturating_sub(rows.start);
if in_file_tree {
// Part of file tree!
- result += &" ".repeat(length);
+ result += &self.render_file_tree(length)?;
} else if y == rows.start && tab_line_enabled {
// Tab line
result += &self.render_tab_line(fc, lua, length)?;
@@ -535,6 +535,12 @@ impl Editor {
Ok(content)
}
+ fn render_file_tree(&mut self, length: usize) -> Result {
+ let editor_bg = Bg(config!(self.config, colors).editor_bg.to_color()?);
+ let editor_fg = Fg(config!(self.config, colors).editor_fg.to_color()?);
+ Ok(format!("{editor_bg}{editor_fg}{}", " ".repeat(length)))
+ }
+
/// Display a prompt in the document
pub fn prompt>(&mut self, prompt: S) -> Result {
let prompt = prompt.into();
From 5da441830931ebcddf419fb8692340b531b6fd2d Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Mon, 2 Dec 2024 13:36:12 +0000
Subject: [PATCH 05/51] File tree will no longer clog up tree and cause crashes
---
src/editor/filetree.rs | 23 +++++++++++++++++++----
src/editor/interface.rs | 2 ++
2 files changed, 21 insertions(+), 4 deletions(-)
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index ab5d0b70..59330487 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -4,13 +4,28 @@ use crate::Editor;
impl Editor {
/// Open the file tree
+ #[allow(clippy::cast_precision_loss)]
pub fn open_file_tree(&mut self) {
if !self.file_tree_is_open() {
// Wrap existing file layout in new file layout
- self.files = FileLayout::SideBySide(vec![
- (FileLayout::FileTree, 0.2),
- (self.files.clone(), 0.8),
- ]);
+ if let FileLayout::SideBySide(ref mut layouts) = &mut self.files {
+ // Shrink existing splits
+ let redistribute = 0.2 / layouts.len() as f64;
+ for (_, prop) in &mut *layouts {
+ if *prop >= redistribute {
+ *prop -= redistribute;
+ } else {
+ *prop = 0.0;
+ }
+ }
+ // Insert file tree
+ layouts.insert(0, (FileLayout::FileTree, 0.2));
+ } else {
+ self.files = FileLayout::SideBySide(vec![
+ (FileLayout::FileTree, 0.2),
+ (self.files.clone(), 0.8),
+ ]);
+ }
self.ptr = vec![0];
}
}
diff --git a/src/editor/interface.rs b/src/editor/interface.rs
index cccc26aa..12fba02b 100644
--- a/src/editor/interface.rs
+++ b/src/editor/interface.rs
@@ -535,6 +535,8 @@ impl Editor {
Ok(content)
}
+ /// Render a line in the file tree
+ #[allow(clippy::similar_names)]
fn render_file_tree(&mut self, length: usize) -> Result {
let editor_bg = Bg(config!(self.config, colors).editor_bg.to_color()?);
let editor_fg = Fg(config!(self.config, colors).editor_fg.to_color()?);
From 2139a93aeb930c2df08f97ec1b3ce27878850a16 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Mon, 2 Dec 2024 15:10:21 +0000
Subject: [PATCH 06/51] Added file tree backend
---
src/editor/filetree.rs | 148 ++++++++++++++++++++++++++++++++++++++++-
src/error.rs | 1 +
2 files changed, 147 insertions(+), 2 deletions(-)
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index 59330487..91baa356 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -1,6 +1,150 @@
-use crate::editor::FileLayout;
/// Utilities for handling the file tree
-use crate::Editor;
+
+use crate::editor::FileLayout;
+use crate::{Editor, Result, OxError};
+use std::path::{Path, PathBuf};
+
+#[derive(Debug)]
+pub enum FileTree {
+ /// Represents a file
+ File {
+ path: String
+ },
+ /// Represents a directory
+ Dir {
+ path: String,
+ /// NOTE: when files is None, it means it has been unexpanded
+ /// directories lazily expand, only when the user requests them to be opened
+ files: Option>,
+ },
+}
+
+impl FileTree {
+ /// Build a file tree from a directory
+ pub fn build(dir: &str) -> Result {
+ // Ensure we have the absolute path
+ let root = std::fs::canonicalize(dir)?;
+ let mut result = Self::build_shallow(&root)?;
+ result.sort();
+ Ok(result)
+ }
+
+ /// Expands into a directory
+ fn build_shallow(path: &PathBuf) -> Result {
+ if path.is_file() {
+ Ok(Self::File { path: Self::path_to_string(path) })
+ } else if path.is_dir() {
+ let mut files = vec![];
+ for entry in std::fs::read_dir(path)? {
+ let entry = entry?;
+ if entry.path().is_file() {
+ files.push(Self::File { path: Self::path_to_string(&entry.path()) });
+ } else if entry.path().is_dir() {
+ files.push(Self::Dir { path: Self::path_to_string(&entry.path()), files: None });
+ }
+ }
+ Ok(Self::Dir {
+ path: Self::path_to_string(path),
+ files: Some(files),
+ })
+ } else {
+ Err(OxError::InvalidPath)
+ }
+ }
+
+ /// Takes a path and turns it into a string
+ fn path_to_string(path: &Path) -> String {
+ let mut path = path.to_string_lossy().to_string();
+ if path.starts_with("\\\\?\\") {
+ path = path[4..].to_string();
+ }
+ path
+ }
+
+ /// Search for and retrieve a mutable reference to a node
+ pub fn get_mut(&mut self, needle: &str) -> Option<&mut Self> {
+ match self {
+ Self::File { path } => {
+ if needle == path {
+ // Match found!
+ Some(self)
+ } else {
+ // No match
+ None
+ }
+ }
+ Self::Dir { path, .. } => {
+ if needle == path {
+ // This directory is what we're searching for
+ Some(self)
+ } else if let Self::Dir { files: Some(files), .. } = self {
+ // Not directly what we're looking for, let's go deeper
+ for file in files {
+ if let Some(result) = file.get_mut(needle) {
+ // Found it! Return upwards
+ return Some(result);
+ }
+ }
+ // None of the files match up
+ None
+ } else {
+ // Dead end
+ None
+ }
+ }
+ }
+ }
+
+ /// Expand a directory downwards
+ pub fn expand(&mut self) {
+ if let Self::Dir { path, .. } = self {
+ // Expand this directory
+ if let Ok(root) = std::fs::canonicalize(path) {
+ if let Ok(mut expanded) = Self::build_shallow(&root) {
+ expanded.sort();
+ *self = expanded;
+ }
+ }
+ }
+ }
+
+ /// Sort a file tree to have directories and files separated and ordered alphabetically
+ fn sort(&mut self) {
+ match self {
+ FileTree::File { .. } => (),
+ FileTree::Dir { files, .. } => {
+ // Sort child directories
+ if let Some(files) = files {
+ for file in files.iter_mut() {
+ file.sort();
+ }
+
+ // Sort this directory
+ files.sort_by(|a, b| {
+ let a_is_dir = matches!(a, FileTree::Dir { .. });
+ let b_is_dir = matches!(b, FileTree::Dir { .. });
+
+ // Directories come first
+ match (a_is_dir, b_is_dir) {
+ (true, false) => std::cmp::Ordering::Less,
+ (false, true) => std::cmp::Ordering::Greater,
+ _ => {
+ // If both are the same type, compare by path
+ let a_path = match a {
+ FileTree::File { path } | FileTree::Dir { path, .. } => path,
+ };
+ let b_path = match b {
+ FileTree::File { path } | FileTree::Dir { path, .. } => path,
+ };
+ a_path.cmp(b_path)
+ }
+ }
+ });
+ }
+ }
+ }
+ }
+}
impl Editor {
/// Open the file tree
diff --git a/src/error.rs b/src/error.rs
index e1f9010c..659d60e6 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -28,6 +28,7 @@ error_set! {
AlreadyOpen {
file: String,
},
+ InvalidPath,
// None, <--- Needed???
};
}
From e643be3616415cc7cae6f1a602a323725bc5df96 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Mon, 2 Dec 2024 15:21:42 +0000
Subject: [PATCH 07/51] rustfmt
---
src/editor/filetree.rs | 27 +++++++++++++++++----------
1 file changed, 17 insertions(+), 10 deletions(-)
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index 91baa356..1c9250b9 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -1,15 +1,12 @@
/// Utilities for handling the file tree
-
use crate::editor::FileLayout;
-use crate::{Editor, Result, OxError};
+use crate::{Editor, OxError, Result};
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub enum FileTree {
/// Represents a file
- File {
- path: String
- },
+ File { path: String },
/// Represents a directory
Dir {
path: String,
@@ -32,15 +29,22 @@ impl FileTree {
/// Expands into a directory
fn build_shallow(path: &PathBuf) -> Result {
if path.is_file() {
- Ok(Self::File { path: Self::path_to_string(path) })
+ Ok(Self::File {
+ path: Self::path_to_string(path),
+ })
} else if path.is_dir() {
let mut files = vec![];
for entry in std::fs::read_dir(path)? {
let entry = entry?;
if entry.path().is_file() {
- files.push(Self::File { path: Self::path_to_string(&entry.path()) });
+ files.push(Self::File {
+ path: Self::path_to_string(&entry.path()),
+ });
} else if entry.path().is_dir() {
- files.push(Self::Dir { path: Self::path_to_string(&entry.path()), files: None });
+ files.push(Self::Dir {
+ path: Self::path_to_string(&entry.path()),
+ files: None,
+ });
}
}
Ok(Self::Dir {
@@ -77,7 +81,10 @@ impl FileTree {
if needle == path {
// This directory is what we're searching for
Some(self)
- } else if let Self::Dir { files: Some(files), .. } = self {
+ } else if let Self::Dir {
+ files: Some(files), ..
+ } = self
+ {
// Not directly what we're looking for, let's go deeper
for file in files {
if let Some(result) = file.get_mut(needle) {
@@ -123,7 +130,7 @@ impl FileTree {
files.sort_by(|a, b| {
let a_is_dir = matches!(a, FileTree::Dir { .. });
let b_is_dir = matches!(b, FileTree::Dir { .. });
-
+
// Directories come first
match (a_is_dir, b_is_dir) {
(true, false) => std::cmp::Ordering::Less,
From 9fee47ff24a82d710811e26f5e5087f421db8e29 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Mon, 2 Dec 2024 16:23:39 +0000
Subject: [PATCH 08/51] Rendered file tree structure
---
src/editor/filetree.rs | 89 ++++++++++++++++++++++++++++++++++++-----
src/editor/interface.rs | 37 +++++++++++++++--
src/editor/mod.rs | 4 ++
3 files changed, 116 insertions(+), 14 deletions(-)
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index 1c9250b9..617efa78 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -2,6 +2,9 @@
use crate::editor::FileLayout;
use crate::{Editor, OxError, Result};
use std::path::{Path, PathBuf};
+use kaolinite::utils::{get_cwd, get_file_name};
+use std::fmt::{Error, Display, Formatter};
+use std::result::Result as RResult;
#[derive(Debug)]
pub enum FileTree {
@@ -118,8 +121,8 @@ impl FileTree {
/// Sort a file tree to have directories and files separated and ordered alphabetically
fn sort(&mut self) {
match self {
- FileTree::File { .. } => (),
- FileTree::Dir { files, .. } => {
+ Self::File { .. } => (),
+ Self::Dir { files, .. } => {
// Sort child directories
if let Some(files) = files {
for file in files.iter_mut() {
@@ -128,22 +131,31 @@ impl FileTree {
// Sort this directory
files.sort_by(|a, b| {
+ let a_is_hidden = a.is_hidden();
+ let b_is_hidden = b.is_hidden();
let a_is_dir = matches!(a, FileTree::Dir { .. });
let b_is_dir = matches!(b, FileTree::Dir { .. });
// Directories come first
- match (a_is_dir, b_is_dir) {
+ match (a_is_hidden, b_is_hidden) {
(true, false) => std::cmp::Ordering::Less,
(false, true) => std::cmp::Ordering::Greater,
_ => {
- // If both are the same type, compare by path
- let a_path = match a {
- FileTree::File { path } | FileTree::Dir { path, .. } => path,
- };
- let b_path = match b {
- FileTree::File { path } | FileTree::Dir { path, .. } => path,
- };
- a_path.cmp(b_path)
+ // If both are the same hidden status, directories come first
+ match (a_is_dir, b_is_dir) {
+ (true, false) => std::cmp::Ordering::Less,
+ (false, true) => std::cmp::Ordering::Greater,
+ _ => {
+ // If both are the same type, compare by path
+ let a_path = match a {
+ FileTree::File { path } | FileTree::Dir { path, .. } => path,
+ };
+ let b_path = match b {
+ FileTree::File { path } | FileTree::Dir { path, .. } => path,
+ };
+ a_path.cmp(b_path)
+ }
+ }
}
}
});
@@ -151,6 +163,56 @@ impl FileTree {
}
}
}
+
+ /// Work out if this node is hidden or not
+ fn is_hidden(&self) -> bool {
+ let path = match self {
+ Self::File { path } | Self::Dir { path, .. } => path,
+ };
+ get_file_name(path).is_some_and(|name| name.starts_with('.'))
+ }
+
+ /// Get the appropriate icon
+ fn icon(&self) -> &str {
+ let is_file = match self {
+ Self::File { .. } => true,
+ Self::Dir { .. } => false,
+ };
+ let is_expanded = match self {
+ Self::File { .. } => false,
+ Self::Dir { files, .. } => files.is_some(),
+ };
+ let is_hidden = self.is_hidden();
+ match (is_file, is_hidden, is_expanded) {
+ // Closed folders
+ (false, false, false) => " ",
+ (false, true, false) => " ",
+ // Opened folders
+ (false, _, true) => " ",
+ // Files
+ (true, false, _) => " ",
+ (true, true, _) => " ",
+ }
+ }
+}
+
+impl Display for FileTree {
+ fn fmt(&self, f: &mut Formatter<'_>) -> RResult<(), Error> {
+ match self {
+ Self::File { path } => writeln!(f, "{}{}", self.icon(), get_file_name(path).unwrap_or(path.to_string()))?,
+ Self::Dir { path, files } => {
+ // Write self
+ writeln!(f, "{}{}", self.icon(), get_file_name(path).unwrap_or(path.to_string()))?;
+ if let Some(files) = files {
+ // Write child nodes
+ for file in files {
+ write!(f, " {file}")?;
+ }
+ }
+ }
+ }
+ Ok(())
+ }
}
impl Editor {
@@ -158,6 +220,11 @@ impl Editor {
#[allow(clippy::cast_precision_loss)]
pub fn open_file_tree(&mut self) {
if !self.file_tree_is_open() {
+ if let Some(cwd) = get_cwd() {
+ if let Ok(ft) = FileTree::build(&cwd) {
+ self.file_tree = Some(ft);
+ }
+ }
// Wrap existing file layout in new file layout
if let FileLayout::SideBySide(ref mut layouts) = &mut self.files {
// Shrink existing splits
diff --git a/src/editor/interface.rs b/src/editor/interface.rs
index 12fba02b..0925aa8d 100644
--- a/src/editor/interface.rs
+++ b/src/editor/interface.rs
@@ -24,6 +24,7 @@ pub struct RenderCache {
pub help_message: Vec<(bool, String)>,
pub help_message_width: usize,
pub help_message_span: Range,
+ pub file_tree: Vec,
}
impl Editor {
@@ -51,6 +52,11 @@ impl Editor {
let help_start = (size.h / 2).saturating_sub(help_length / 2) + 1;
let help_end = help_start + help_length;
self.render_cache.help_message_span = help_start..help_end + 1;
+ // Calculate file tree display representation
+ if let Some(file_tree) = self.file_tree.as_ref() {
+ let filetree = format!("{file_tree}");
+ self.render_cache.file_tree = filetree.split('\n').map(std::string::ToString::to_string).collect();
+ }
}
/// Render a specific line
@@ -99,7 +105,7 @@ impl Editor {
let rel_y = y.saturating_sub(rows.start);
if in_file_tree {
// Part of file tree!
- result += &self.render_file_tree(length)?;
+ result += &self.render_file_tree(y, length)?;
} else if y == rows.start && tab_line_enabled {
// Tab line
result += &self.render_tab_line(fc, lua, length)?;
@@ -537,10 +543,35 @@ impl Editor {
/// Render a line in the file tree
#[allow(clippy::similar_names)]
- fn render_file_tree(&mut self, length: usize) -> Result {
+ fn render_file_tree(&mut self, y: usize, length: usize) -> Result {
let editor_bg = Bg(config!(self.config, colors).editor_bg.to_color()?);
let editor_fg = Fg(config!(self.config, colors).editor_fg.to_color()?);
- Ok(format!("{editor_bg}{editor_fg}{}", " ".repeat(length)))
+ // Work out which line to use
+ let line = self.render_cache
+ .file_tree
+ .get(y)
+ .map(std::string::ToString::to_string)
+ .unwrap_or_default();
+ let mut line = line.chars();
+ // Trim to fit
+ let mut result = String::new();
+ let mut available = length;
+ loop {
+ if available > 0 {
+ if let Some(next) = line.next() {
+ let ch = width_char(&next, 4);
+ available = available.saturating_sub(ch);
+ result.push(next);
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ result += &" ".repeat(available);
+ // Return result
+ Ok(format!("{editor_bg}{editor_fg}{result}"))
}
/// Display a prompt in the document
diff --git a/src/editor/mod.rs b/src/editor/mod.rs
index 46e630ba..786c78a3 100644
--- a/src/editor/mod.rs
+++ b/src/editor/mod.rs
@@ -31,6 +31,7 @@ pub use documents::{FileContainer, FileLayout};
pub use filetypes::{FileType, FileTypes};
pub use interface::RenderCache;
pub use macros::MacroMan;
+pub use filetree::FileTree;
/// For managing all editing and rendering of cactus
#[allow(clippy::struct_excessive_bools)]
@@ -71,6 +72,8 @@ pub struct Editor {
pub macro_man: MacroMan,
/// Render cache
pub render_cache: RenderCache,
+ /// For storing the current file tree value
+ pub file_tree: Option,
}
impl Editor {
@@ -96,6 +99,7 @@ impl Editor {
alt_click_state: None,
macro_man: MacroMan::default(),
render_cache: RenderCache::default(),
+ file_tree: None,
})
}
From c3b138b2692367638ed98207c55681add7f7a893 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Mon, 2 Dec 2024 16:24:40 +0000
Subject: [PATCH 09/51] rustfmt
---
src/editor/filetree.rs | 24 ++++++++++++++++++------
src/editor/interface.rs | 8 ++++++--
src/editor/mod.rs | 2 +-
3 files changed, 25 insertions(+), 9 deletions(-)
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index 617efa78..ad4fe60e 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -1,9 +1,9 @@
/// Utilities for handling the file tree
use crate::editor::FileLayout;
use crate::{Editor, OxError, Result};
-use std::path::{Path, PathBuf};
use kaolinite::utils::{get_cwd, get_file_name};
-use std::fmt::{Error, Display, Formatter};
+use std::fmt::{Display, Error, Formatter};
+use std::path::{Path, PathBuf};
use std::result::Result as RResult;
#[derive(Debug)]
@@ -148,10 +148,12 @@ impl FileTree {
_ => {
// If both are the same type, compare by path
let a_path = match a {
- FileTree::File { path } | FileTree::Dir { path, .. } => path,
+ FileTree::File { path }
+ | FileTree::Dir { path, .. } => path,
};
let b_path = match b {
- FileTree::File { path } | FileTree::Dir { path, .. } => path,
+ FileTree::File { path }
+ | FileTree::Dir { path, .. } => path,
};
a_path.cmp(b_path)
}
@@ -199,10 +201,20 @@ impl FileTree {
impl Display for FileTree {
fn fmt(&self, f: &mut Formatter<'_>) -> RResult<(), Error> {
match self {
- Self::File { path } => writeln!(f, "{}{}", self.icon(), get_file_name(path).unwrap_or(path.to_string()))?,
+ Self::File { path } => writeln!(
+ f,
+ "{}{}",
+ self.icon(),
+ get_file_name(path).unwrap_or(path.to_string())
+ )?,
Self::Dir { path, files } => {
// Write self
- writeln!(f, "{}{}", self.icon(), get_file_name(path).unwrap_or(path.to_string()))?;
+ writeln!(
+ f,
+ "{}{}",
+ self.icon(),
+ get_file_name(path).unwrap_or(path.to_string())
+ )?;
if let Some(files) = files {
// Write child nodes
for file in files {
diff --git a/src/editor/interface.rs b/src/editor/interface.rs
index 0925aa8d..15ae559b 100644
--- a/src/editor/interface.rs
+++ b/src/editor/interface.rs
@@ -55,7 +55,10 @@ impl Editor {
// Calculate file tree display representation
if let Some(file_tree) = self.file_tree.as_ref() {
let filetree = format!("{file_tree}");
- self.render_cache.file_tree = filetree.split('\n').map(std::string::ToString::to_string).collect();
+ self.render_cache.file_tree = filetree
+ .split('\n')
+ .map(std::string::ToString::to_string)
+ .collect();
}
}
@@ -547,7 +550,8 @@ impl Editor {
let editor_bg = Bg(config!(self.config, colors).editor_bg.to_color()?);
let editor_fg = Fg(config!(self.config, colors).editor_fg.to_color()?);
// Work out which line to use
- let line = self.render_cache
+ let line = self
+ .render_cache
.file_tree
.get(y)
.map(std::string::ToString::to_string)
diff --git a/src/editor/mod.rs b/src/editor/mod.rs
index 786c78a3..48a79fcf 100644
--- a/src/editor/mod.rs
+++ b/src/editor/mod.rs
@@ -28,10 +28,10 @@ mod scanning;
pub use cursor::{allowed_by_multi_cursor, handle_multiple_cursors};
pub use documents::{FileContainer, FileLayout};
+pub use filetree::FileTree;
pub use filetypes::{FileType, FileTypes};
pub use interface::RenderCache;
pub use macros::MacroMan;
-pub use filetree::FileTree;
/// For managing all editing and rendering of cactus
#[allow(clippy::struct_excessive_bools)]
From 5b78a7dfadec61b4960a61c3d2861b5e301219c8 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Mon, 2 Dec 2024 17:22:01 +0000
Subject: [PATCH 10/51] Better focus and added basic tree selection
---
src/editor/filetree.rs | 67 ++++++++++++++++++++++++++++++-----------
src/editor/interface.rs | 18 +++++++----
src/editor/mod.rs | 6 ++++
3 files changed, 68 insertions(+), 23 deletions(-)
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index ad4fe60e..918fdd72 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -2,9 +2,7 @@
use crate::editor::FileLayout;
use crate::{Editor, OxError, Result};
use kaolinite::utils::{get_cwd, get_file_name};
-use std::fmt::{Display, Error, Formatter};
use std::path::{Path, PathBuf};
-use std::result::Result as RResult;
#[derive(Debug)]
pub enum FileTree {
@@ -196,34 +194,58 @@ impl FileTree {
(true, true, _) => " ",
}
}
-}
-impl Display for FileTree {
- fn fmt(&self, f: &mut Formatter<'_>) -> RResult<(), Error> {
+ /// Work out if this node is selected
+ pub fn is_selected(&self, selection: &str) -> bool {
+ match self {
+ Self::File { path } | Self::Dir { path, .. } => path == selection,
+ }
+ }
+
+ /// Display this file tree
+ pub fn display(&self, selection: &str) -> (Vec, Option) {
match self {
- Self::File { path } => writeln!(
- f,
- "{}{}",
- self.icon(),
- get_file_name(path).unwrap_or(path.to_string())
- )?,
+ Self::File { path } => (
+ vec![format!(
+ "{}{}",
+ self.icon(),
+ get_file_name(path).unwrap_or(path.to_string())
+ )],
+ if self.is_selected(selection) {
+ Some(0)
+ } else {
+ None
+ },
+ ),
Self::Dir { path, files } => {
+ let mut result = vec![];
+ let mut at = None;
// Write self
- writeln!(
- f,
+ result.push(format!(
"{}{}",
self.icon(),
get_file_name(path).unwrap_or(path.to_string())
- )?;
+ ));
+ if self.is_selected(selection) {
+ at = Some(result.len().saturating_sub(1));
+ }
+ // Write child nodes
if let Some(files) = files {
- // Write child nodes
for file in files {
- write!(f, " {file}")?;
+ let (sub_display, sub_at) = file.display(selection);
+ for (c, s) in sub_display.iter().enumerate() {
+ result.push(format!(" {s}"));
+ if let Some(sub_at) = sub_at {
+ if sub_at == c {
+ at = Some(result.len().saturating_sub(1));
+ }
+ }
+ }
}
}
+ (result, at)
}
}
- Ok(())
}
}
@@ -232,9 +254,11 @@ impl Editor {
#[allow(clippy::cast_precision_loss)]
pub fn open_file_tree(&mut self) {
if !self.file_tree_is_open() {
+ self.old_ptr = self.ptr.clone();
if let Some(cwd) = get_cwd() {
if let Ok(ft) = FileTree::build(&cwd) {
self.file_tree = Some(ft);
+ self.file_tree_selection = Some(cwd);
}
}
// Wrap existing file layout in new file layout
@@ -270,6 +294,15 @@ impl Editor {
if let Some(at) = ftp {
// Delete the file tree
self.files.remove(vec![at]);
+ // Clear up any leftovers sidebyside
+ if let FileLayout::SideBySide(layouts) = &self.files {
+ if layouts.len() == 1 {
+ // Remove leftover
+ self.files = layouts[0].0.clone();
+ }
+ }
+ // Reset pointer back to what it used to be
+ self.ptr = self.old_ptr.clone();
}
}
}
diff --git a/src/editor/interface.rs b/src/editor/interface.rs
index 15ae559b..c2f63550 100644
--- a/src/editor/interface.rs
+++ b/src/editor/interface.rs
@@ -25,6 +25,7 @@ pub struct RenderCache {
pub help_message_width: usize,
pub help_message_span: Range,
pub file_tree: Vec,
+ pub file_tree_selection: Option,
}
impl Editor {
@@ -54,11 +55,10 @@ impl Editor {
self.render_cache.help_message_span = help_start..help_end + 1;
// Calculate file tree display representation
if let Some(file_tree) = self.file_tree.as_ref() {
- let filetree = format!("{file_tree}");
- self.render_cache.file_tree = filetree
- .split('\n')
- .map(std::string::ToString::to_string)
- .collect();
+ let (files, sel) =
+ file_tree.display(self.file_tree_selection.as_ref().unwrap_or(&String::new()));
+ self.render_cache.file_tree = files;
+ self.render_cache.file_tree_selection = sel;
}
}
@@ -549,6 +549,8 @@ impl Editor {
fn render_file_tree(&mut self, y: usize, length: usize) -> Result {
let editor_bg = Bg(config!(self.config, colors).editor_bg.to_color()?);
let editor_fg = Fg(config!(self.config, colors).editor_fg.to_color()?);
+ let selection_bg = Bg(config!(self.config, colors).selection_bg.to_color()?);
+ let selection_fg = Fg(config!(self.config, colors).selection_fg.to_color()?);
// Work out which line to use
let line = self
.render_cache
@@ -575,7 +577,11 @@ impl Editor {
}
result += &" ".repeat(available);
// Return result
- Ok(format!("{editor_bg}{editor_fg}{result}"))
+ if self.render_cache.file_tree_selection == Some(y) {
+ Ok(format!("{selection_bg}{selection_fg}{result}"))
+ } else {
+ Ok(format!("{editor_bg}{editor_fg}{result}"))
+ }
}
/// Display a prompt in the document
diff --git a/src/editor/mod.rs b/src/editor/mod.rs
index 48a79fcf..4b48a8b0 100644
--- a/src/editor/mod.rs
+++ b/src/editor/mod.rs
@@ -74,6 +74,10 @@ pub struct Editor {
pub render_cache: RenderCache,
/// For storing the current file tree value
pub file_tree: Option,
+ /// The selected file in the file tree
+ pub file_tree_selection: Option,
+ /// For caching a pointer to go back to when in a file tree
+ pub old_ptr: Vec,
}
impl Editor {
@@ -100,6 +104,8 @@ impl Editor {
macro_man: MacroMan::default(),
render_cache: RenderCache::default(),
file_tree: None,
+ file_tree_selection: None,
+ old_ptr: vec![],
})
}
From 1c7d932e3d29626269085cc051a1ba3952ef5097 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Mon, 2 Dec 2024 18:02:57 +0000
Subject: [PATCH 11/51] Further made autoindent resistant to errors and moving
filetree selection
---
plugins/autoindent.lua | 116 ++++++++++++++++++++++-------------------
src/editor/filetree.rs | 17 ++++++
src/editor/mod.rs | 42 ++++++++++++++-
3 files changed, 119 insertions(+), 56 deletions(-)
diff --git a/plugins/autoindent.lua b/plugins/autoindent.lua
index 8c709407..3ddd1645 100644
--- a/plugins/autoindent.lua
+++ b/plugins/autoindent.lua
@@ -162,16 +162,18 @@ function autoindent:disperse_block()
end
event_mapping["enter"] = function()
- -- Indent where appropriate
- if autoindent:causes_indent(editor.cursor.y - 1) then
- local new_level = autoindent:get_indent(editor.cursor.y) + 1
- autoindent:set_indent(editor.cursor.y, new_level)
+ if editor.cursor ~= nil then
+ -- Indent where appropriate
+ if autoindent:causes_indent(editor.cursor.y - 1) then
+ local new_level = autoindent:get_indent(editor.cursor.y) + 1
+ autoindent:set_indent(editor.cursor.y, new_level)
+ end
+ -- Give newly created line a boost to match it up relatively with the line before it
+ local added_level = autoindent:get_indent(editor.cursor.y) + autoindent:get_indent(editor.cursor.y - 1)
+ autoindent:set_indent(editor.cursor.y, added_level)
+ -- Handle the case where enter is pressed, creating a multi-line block that requires neatening up
+ autoindent:disperse_block()
end
- -- Give newly created line a boost to match it up relatively with the line before it
- local added_level = autoindent:get_indent(editor.cursor.y) + autoindent:get_indent(editor.cursor.y - 1)
- autoindent:set_indent(editor.cursor.y, added_level)
- -- Handle the case where enter is pressed, creating a multi-line block that requires neatening up
- autoindent:disperse_block()
end
-- For each ascii characters and punctuation
@@ -210,62 +212,66 @@ end
-- Shortcut to indent a selection
event_mapping["ctrl_tab"] = function()
- local cursor = editor.cursor
- local select = editor.selection
- if cursor.y == select.y then
- -- Single line is selected
- local level = autoindent:get_indent(cursor.y)
- autoindent:set_indent(cursor.y, level + 1)
- else
- -- Multiple lines selected
- if cursor.y > select.y then
- for line = select.y, cursor.y do
- editor:move_to(0, line)
- local indent = autoindent:get_indent(line)
- autoindent:set_indent(line, indent + 1)
- end
+ if editor.cursor ~= nil then
+ local cursor = editor.cursor
+ local select = editor.selection
+ if cursor.y == select.y then
+ -- Single line is selected
+ local level = autoindent:get_indent(cursor.y)
+ autoindent:set_indent(cursor.y, level + 1)
else
- for line = cursor.y, select.y do
- editor:move_to(0, line)
- local indent = autoindent:get_indent(line)
- autoindent:set_indent(line, indent + 1)
+ -- Multiple lines selected
+ if cursor.y > select.y then
+ for line = select.y, cursor.y do
+ editor:move_to(0, line)
+ local indent = autoindent:get_indent(line)
+ autoindent:set_indent(line, indent + 1)
+ end
+ else
+ for line = cursor.y, select.y do
+ editor:move_to(0, line)
+ local indent = autoindent:get_indent(line)
+ autoindent:set_indent(line, indent + 1)
+ end
end
+ local cursor_tabs = dedent_amount(cursor.y)
+ local select_tabs = dedent_amount(select.y)
+ editor:move_to(cursor.x + cursor_tabs, cursor.y)
+ editor:select_to(select.x + select_tabs, select.y)
end
- local cursor_tabs = dedent_amount(cursor.y)
- local select_tabs = dedent_amount(select.y)
- editor:move_to(cursor.x + cursor_tabs, cursor.y)
- editor:select_to(select.x + select_tabs, select.y)
+ editor:cursor_snap()
end
- editor:cursor_snap()
end
-- Shortcut to dedent a line
event_mapping["shift_tab"] = function()
- local cursor = editor.cursor
- local select = editor.selection
- if cursor.x == select.x and cursor.y == select.y then
- -- Dedent a single line
- local level = autoindent:get_indent(editor.cursor.y)
- autoindent:set_indent(editor.cursor.y, level - 1)
- else
- -- Dedent a group of lines
- if cursor.y > select.y then
- for line = select.y, cursor.y do
- editor:move_to(0, line)
- local indent = autoindent:get_indent(line)
- autoindent:set_indent(line, indent - 1)
- end
+ if editor.cursor ~= nil then
+ local cursor = editor.cursor
+ local select = editor.selection
+ if cursor.x == select.x and cursor.y == select.y then
+ -- Dedent a single line
+ local level = autoindent:get_indent(editor.cursor.y)
+ autoindent:set_indent(editor.cursor.y, level - 1)
else
- for line = cursor.y, select.y do
- editor:move_to(0, line)
- local indent = autoindent:get_indent(line)
- autoindent:set_indent(line, indent - 1)
+ -- Dedent a group of lines
+ if cursor.y > select.y then
+ for line = select.y, cursor.y do
+ editor:move_to(0, line)
+ local indent = autoindent:get_indent(line)
+ autoindent:set_indent(line, indent - 1)
+ end
+ else
+ for line = cursor.y, select.y do
+ editor:move_to(0, line)
+ local indent = autoindent:get_indent(line)
+ autoindent:set_indent(line, indent - 1)
+ end
end
+ local cursor_tabs = dedent_amount(cursor.y)
+ local select_tabs = dedent_amount(select.y)
+ editor:move_to(cursor.x - cursor_tabs, cursor.y)
+ editor:select_to(select.x - select_tabs, select.y)
end
- local cursor_tabs = dedent_amount(cursor.y)
- local select_tabs = dedent_amount(select.y)
- editor:move_to(cursor.x - cursor_tabs, cursor.y)
- editor:select_to(select.x - select_tabs, select.y)
+ editor:cursor_snap()
end
- editor:cursor_snap()
end
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index 918fdd72..0ffc6dbd 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -247,6 +247,23 @@ impl FileTree {
}
}
}
+
+ /// Find the file path at a certain index
+ pub fn flatten(&self) -> Vec {
+ match self {
+ Self::File { path } => vec![path.to_string()],
+ Self::Dir { path, files } => {
+ let mut result = vec![];
+ result.push(path.to_string());
+ if let Some(files) = files {
+ for file in files {
+ result.append(&mut file.flatten());
+ }
+ }
+ result
+ }
+ }
+ }
}
impl Editor {
diff --git a/src/editor/mod.rs b/src/editor/mod.rs
index 4b48a8b0..f9e6e997 100644
--- a/src/editor/mod.rs
+++ b/src/editor/mod.rs
@@ -442,7 +442,47 @@ impl Editor {
/// Handle key event
pub fn handle_key_event(&mut self, modifiers: KMod, code: KCode) -> Result<()> {
- if self.try_doc().is_some() {
+ let in_file_tree = matches!(
+ self.files.get_raw(self.ptr.clone()),
+ Some(FileLayout::FileTree)
+ );
+ if in_file_tree {
+ // File tree key behaviour
+ match (modifiers, code) {
+ (KMod::NONE, KCode::Up) => {
+ if let Some(ref mut fts) = self.render_cache.file_tree_selection {
+ // Move up a file (in the render cache)
+ *fts = fts.saturating_sub(1);
+ // Move up a file (in the backend)
+ let flat = self
+ .file_tree
+ .as_ref()
+ .map(FileTree::flatten)
+ .unwrap_or_default();
+ let new_path = flat.get(*fts);
+ self.file_tree_selection = new_path.cloned();
+ }
+ }
+ (KMod::NONE, KCode::Down) => {
+ if let Some(ref mut fts) = self.render_cache.file_tree_selection {
+ let flat = self
+ .file_tree
+ .as_ref()
+ .map(FileTree::flatten)
+ .unwrap_or_default();
+ if *fts + 1 < flat.len() {
+ // Move up a file (in the render cache)
+ *fts += 1;
+ // Move up a file (in the backend)
+ let new_path = flat.get(*fts);
+ self.file_tree_selection = new_path.cloned();
+ }
+ }
+ }
+ _ => (),
+ }
+ } else {
+ // Non file tree behaviour
// Check period of inactivity
let end = Instant::now();
let inactivity = end.duration_since(self.last_active).as_millis() as usize;
From cef48ce8975310ba1f313cec8f8ff06a956c1baf Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Tue, 3 Dec 2024 13:31:57 +0000
Subject: [PATCH 12/51] Small refactor, better focusing
---
src/config/editor.rs | 8 ++++-
src/editor/filetree.rs | 68 ++++++++++++++++++++++++++++++++++++++++--
src/editor/mod.rs | 33 ++------------------
3 files changed, 76 insertions(+), 33 deletions(-)
diff --git a/src/config/editor.rs b/src/config/editor.rs
index c8f15622..3fc667e7 100644
--- a/src/config/editor.rs
+++ b/src/config/editor.rs
@@ -660,7 +660,13 @@ impl LuaUserData for Editor {
Ok(())
});
methods.add_method_mut("focus_split_left", |_, editor, ()| {
- editor.ptr = FileLayout::move_left(editor.ptr.clone(), &editor.render_cache.span);
+ let new_ptr = FileLayout::move_left(editor.ptr.clone(), &editor.render_cache.span);
+ if matches!(editor.files.get_raw(new_ptr.clone()), Some(FileLayout::FileTree)) {
+ // We just entered into a file tree, cache where we were (minus the file tree itself)
+ editor.old_ptr = editor.ptr.clone();
+ editor.old_ptr.pop();
+ }
+ editor.ptr = new_ptr;
Ok(())
});
methods.add_method_mut("focus_split_right", |_, editor, ()| {
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index 0ffc6dbd..9a332bb7 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -4,6 +4,7 @@ use crate::{Editor, OxError, Result};
use kaolinite::utils::{get_cwd, get_file_name};
use std::path::{Path, PathBuf};
+/// The backend of a file tree - stores the structure of the files and directories
#[derive(Debug)]
pub enum FileTree {
/// Represents a file
@@ -304,6 +305,7 @@ impl Editor {
/// Close the file tree
pub fn close_file_tree(&mut self) {
if let Some(FileLayout::SideBySide(layouts)) = self.files.get_raw(vec![]) {
+ let in_file_tree = matches!(self.files.get_raw(self.ptr.clone()), Some(FileLayout::FileTree));
// Locate where the file tree is
let ftp = layouts
.iter()
@@ -318,8 +320,14 @@ impl Editor {
self.files = layouts[0].0.clone();
}
}
- // Reset pointer back to what it used to be
- self.ptr = self.old_ptr.clone();
+ // Reset pointer back to what it used to be IF we're in the file tree
+ if in_file_tree {
+ self.ptr = self.old_ptr.clone();
+ } else if !self.ptr.is_empty() {
+ // If we're outside the file tree
+ // just take the existing pointer and remove file tree aspect
+ self.ptr.remove(0);
+ }
}
}
}
@@ -343,4 +351,60 @@ impl Editor {
false
}
}
+
+ /// Move file tree selection upwards
+ pub fn file_tree_select_up(&mut self) {
+ if let Some(ref mut fts) = self.render_cache.file_tree_selection {
+ // Move up a file (in the render cache)
+ *fts = fts.saturating_sub(1);
+ // Move up a file (in the backend)
+ let flat = self
+ .file_tree
+ .as_ref()
+ .map(FileTree::flatten)
+ .unwrap_or_default();
+ let new_path = flat.get(*fts);
+ self.file_tree_selection = new_path.cloned();
+ }
+ }
+
+ /// Move file tree selection upwards
+ pub fn file_tree_select_down(&mut self) {
+ if let Some(ref mut fts) = self.render_cache.file_tree_selection {
+ let flat = self
+ .file_tree
+ .as_ref()
+ .map(FileTree::flatten)
+ .unwrap_or_default();
+ if *fts + 1 < flat.len() {
+ // Move up a file (in the render cache)
+ *fts += 1;
+ // Move up a file (in the backend)
+ let new_path = flat.get(*fts);
+ self.file_tree_selection = new_path.cloned();
+ }
+ }
+ }
+
+ /// Open a file from the file tree
+ pub fn file_tree_open_file(&mut self) -> Result<()> {
+ // Default behaviour is open a file in the background and return to file tree
+ if let Some(file_name) = &self.file_tree_selection.clone() {
+ // Quickly restore to old pointer
+ let mut temp = self.old_ptr.clone();
+ if let Some(part) = temp.get_mut(0) {
+ *part += 1;
+ } else {
+ temp = vec![1];
+ }
+ std::mem::swap(&mut temp, &mut self.ptr);
+ // Perform open operation
+ self.open(file_name)?;
+ self.next();
+ self.update_cwd();
+ // Restore old pointer
+ std::mem::swap(&mut temp, &mut self.ptr);
+ }
+ Ok(())
+ }
}
diff --git a/src/editor/mod.rs b/src/editor/mod.rs
index f9e6e997..e21c25e3 100644
--- a/src/editor/mod.rs
+++ b/src/editor/mod.rs
@@ -449,36 +449,9 @@ impl Editor {
if in_file_tree {
// File tree key behaviour
match (modifiers, code) {
- (KMod::NONE, KCode::Up) => {
- if let Some(ref mut fts) = self.render_cache.file_tree_selection {
- // Move up a file (in the render cache)
- *fts = fts.saturating_sub(1);
- // Move up a file (in the backend)
- let flat = self
- .file_tree
- .as_ref()
- .map(FileTree::flatten)
- .unwrap_or_default();
- let new_path = flat.get(*fts);
- self.file_tree_selection = new_path.cloned();
- }
- }
- (KMod::NONE, KCode::Down) => {
- if let Some(ref mut fts) = self.render_cache.file_tree_selection {
- let flat = self
- .file_tree
- .as_ref()
- .map(FileTree::flatten)
- .unwrap_or_default();
- if *fts + 1 < flat.len() {
- // Move up a file (in the render cache)
- *fts += 1;
- // Move up a file (in the backend)
- let new_path = flat.get(*fts);
- self.file_tree_selection = new_path.cloned();
- }
- }
- }
+ (KMod::NONE, KCode::Up) => self.file_tree_select_up(),
+ (KMod::NONE, KCode::Down) => self.file_tree_select_down(),
+ (KMod::NONE, KCode::Enter) => self.file_tree_open_file()?,
_ => (),
}
} else {
From c31cf0402349c8b4f17e4b2f921a574263e0893b Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Tue, 3 Dec 2024 13:33:27 +0000
Subject: [PATCH 13/51] rustfmt
---
src/config/editor.rs | 6 +++++-
src/editor/filetree.rs | 5 ++++-
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/config/editor.rs b/src/config/editor.rs
index 3fc667e7..12e340a1 100644
--- a/src/config/editor.rs
+++ b/src/config/editor.rs
@@ -661,7 +661,11 @@ impl LuaUserData for Editor {
});
methods.add_method_mut("focus_split_left", |_, editor, ()| {
let new_ptr = FileLayout::move_left(editor.ptr.clone(), &editor.render_cache.span);
- if matches!(editor.files.get_raw(new_ptr.clone()), Some(FileLayout::FileTree)) {
+ let in_file_tree = matches!(
+ editor.files.get_raw(new_ptr.clone()),
+ Some(FileLayout::FileTree)
+ );
+ if in_file_tree {
// We just entered into a file tree, cache where we were (minus the file tree itself)
editor.old_ptr = editor.ptr.clone();
editor.old_ptr.pop();
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index 9a332bb7..73bda788 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -305,7 +305,10 @@ impl Editor {
/// Close the file tree
pub fn close_file_tree(&mut self) {
if let Some(FileLayout::SideBySide(layouts)) = self.files.get_raw(vec![]) {
- let in_file_tree = matches!(self.files.get_raw(self.ptr.clone()), Some(FileLayout::FileTree));
+ let in_file_tree = matches!(
+ self.files.get_raw(self.ptr.clone()),
+ Some(FileLayout::FileTree)
+ );
// Locate where the file tree is
let ftp = layouts
.iter()
From df6ae220d24bf2d5139bbcd13809969cba9282cc Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Tue, 3 Dec 2024 13:58:17 +0000
Subject: [PATCH 14/51] Improved opening files from the file tree
---
src/editor/filetree.rs | 17 ++++-------------
src/editor/mod.rs | 12 ++++++++----
2 files changed, 12 insertions(+), 17 deletions(-)
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index 73bda788..173da67b 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -305,10 +305,7 @@ impl Editor {
/// Close the file tree
pub fn close_file_tree(&mut self) {
if let Some(FileLayout::SideBySide(layouts)) = self.files.get_raw(vec![]) {
- let in_file_tree = matches!(
- self.files.get_raw(self.ptr.clone()),
- Some(FileLayout::FileTree)
- );
+ let in_file_tree = matches!(self.files.get_raw(self.ptr.clone()), Some(FileLayout::FileTree));
// Locate where the file tree is
let ftp = layouts
.iter()
@@ -393,20 +390,14 @@ impl Editor {
pub fn file_tree_open_file(&mut self) -> Result<()> {
// Default behaviour is open a file in the background and return to file tree
if let Some(file_name) = &self.file_tree_selection.clone() {
- // Quickly restore to old pointer
+ // Restore to old pointer to open
let mut temp = self.old_ptr.clone();
- if let Some(part) = temp.get_mut(0) {
- *part += 1;
- } else {
- temp = vec![1];
- }
- std::mem::swap(&mut temp, &mut self.ptr);
+ temp.insert(0, 1);
+ self.ptr = temp;
// Perform open operation
self.open(file_name)?;
self.next();
self.update_cwd();
- // Restore old pointer
- std::mem::swap(&mut temp, &mut self.ptr);
}
Ok(())
}
diff --git a/src/editor/mod.rs b/src/editor/mod.rs
index e21c25e3..2845e95d 100644
--- a/src/editor/mod.rs
+++ b/src/editor/mod.rs
@@ -326,6 +326,7 @@ impl Editor {
pub fn quit(&mut self) -> Result<()> {
// Get the atom we're currently at
if let Some((fcs, ptr)) = self.files.get_atom(self.ptr.clone()) {
+ let last_file = fcs.len() == 1;
// Remove the file that is currently open and selected
let msg = "This document isn't saved, press Ctrl + Q to force quit or Esc to cancel";
let doc = &fcs[ptr].doc;
@@ -334,10 +335,13 @@ impl Editor {
fcs.remove(*ptr);
self.prev();
}
- // Clean up the file structure
- self.files.clean_up();
- // Find a new pointer position
- self.ptr = self.files.new_pointer_position(&self.ptr);
+ // Perform cleanup / pointer reassignment if this atom is now empty
+ if last_file {
+ // Clean up the file structure
+ self.files.clean_up();
+ // Find a new pointer position
+ self.ptr = self.files.new_pointer_position(&self.ptr);
+ }
}
// If there are no longer any active atoms, quit the entire editor
self.active = !matches!(self.files, FileLayout::None);
From 4446a4da6b9259aeb5dac533517473a1aa3d759a Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Tue, 3 Dec 2024 14:05:53 +0000
Subject: [PATCH 15/51] Fixed error with handling event errors
---
src/editor/filetree.rs | 5 ++++-
src/main.rs | 10 ++++++++--
2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index 173da67b..c370cffe 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -305,7 +305,10 @@ impl Editor {
/// Close the file tree
pub fn close_file_tree(&mut self) {
if let Some(FileLayout::SideBySide(layouts)) = self.files.get_raw(vec![]) {
- let in_file_tree = matches!(self.files.get_raw(self.ptr.clone()), Some(FileLayout::FileTree));
+ let in_file_tree = matches!(
+ self.files.get_raw(self.ptr.clone()),
+ Some(FileLayout::FileTree)
+ );
// Locate where the file tree is
let ftp = layouts
.iter()
diff --git a/src/main.rs b/src/main.rs
index c397ff55..b0d856be 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -246,8 +246,14 @@ fn handle_event(editor: &AnyUserData, event: &CEvent, lua: &Lua) -> Result<()> {
}
// Actually handle editor event (errors included)
- if let Err(err) = ged!(mut &editor).handle_event(lua, event.clone()) {
- ged!(mut &editor).feedback = Feedback::Error(format!("{err:?}"));
+ let event_result = ged!(mut &editor).handle_event(lua, event.clone());
+ if let Err(err) = event_result {
+ // Nicely display error to user
+ match err {
+ OxError::Lua(err) => handle_lua_error("event", Err(err), &mut ged!(mut &editor).feedback),
+ OxError::AlreadyOpen { file } => ged!(mut &editor).feedback = Feedback::Error(format!("File '{file}' is already open")),
+ _ => ged!(mut &editor).feedback = Feedback::Error(format!("{err:?}")),
+ }
}
// Handle paste event (after event)
From 46ae6f8e44caf65426891d84f0ac8802dafaf3fd Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Tue, 3 Dec 2024 14:18:26 +0000
Subject: [PATCH 16/51] Directory expansion
---
src/editor/filetree.rs | 32 +++++++++++++++++++++++++++++++-
src/editor/mod.rs | 2 +-
src/main.rs | 9 +++++++--
3 files changed, 39 insertions(+), 4 deletions(-)
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index c370cffe..987aaa32 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -1,7 +1,7 @@
/// Utilities for handling the file tree
use crate::editor::FileLayout;
use crate::{Editor, OxError, Result};
-use kaolinite::utils::{get_cwd, get_file_name};
+use kaolinite::utils::{file_or_dir, get_cwd, get_file_name};
use std::path::{Path, PathBuf};
/// The backend of a file tree - stores the structure of the files and directories
@@ -389,6 +389,18 @@ impl Editor {
}
}
+ /// Open a certain file / directory in a file tree
+ pub fn file_tree_open_node(&mut self) -> Result<()> {
+ if let Some(file_name) = &self.file_tree_selection.clone() {
+ match file_or_dir(file_name) {
+ "file" => self.file_tree_open_file()?,
+ "directory" => self.file_tree_toggle_dir(),
+ _ => (),
+ }
+ }
+ Ok(())
+ }
+
/// Open a file from the file tree
pub fn file_tree_open_file(&mut self) -> Result<()> {
// Default behaviour is open a file in the background and return to file tree
@@ -404,4 +416,22 @@ impl Editor {
}
Ok(())
}
+
+ pub fn file_tree_toggle_dir(&mut self) {
+ if let Some(ref mut file_tree) = &mut self.file_tree {
+ if let Some(file_name) = self.file_tree_selection.as_ref() {
+ if let Some(node) = file_tree.get_mut(file_name) {
+ if let FileTree::Dir { files, .. } = node {
+ if files.is_some() {
+ // Clear expansion if already expanded
+ *files = None;
+ } else {
+ // Expand if not already expanded
+ node.expand();
+ }
+ }
+ }
+ }
+ }
+ }
}
diff --git a/src/editor/mod.rs b/src/editor/mod.rs
index 2845e95d..06f7e7a3 100644
--- a/src/editor/mod.rs
+++ b/src/editor/mod.rs
@@ -455,7 +455,7 @@ impl Editor {
match (modifiers, code) {
(KMod::NONE, KCode::Up) => self.file_tree_select_up(),
(KMod::NONE, KCode::Down) => self.file_tree_select_down(),
- (KMod::NONE, KCode::Enter) => self.file_tree_open_file()?,
+ (KMod::NONE, KCode::Enter) => self.file_tree_open_node()?,
_ => (),
}
} else {
diff --git a/src/main.rs b/src/main.rs
index b0d856be..a1e0a430 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -250,8 +250,13 @@ fn handle_event(editor: &AnyUserData, event: &CEvent, lua: &Lua) -> Result<()> {
if let Err(err) = event_result {
// Nicely display error to user
match err {
- OxError::Lua(err) => handle_lua_error("event", Err(err), &mut ged!(mut &editor).feedback),
- OxError::AlreadyOpen { file } => ged!(mut &editor).feedback = Feedback::Error(format!("File '{file}' is already open")),
+ OxError::Lua(err) => {
+ handle_lua_error("event", Err(err), &mut ged!(mut &editor).feedback)
+ }
+ OxError::AlreadyOpen { file } => {
+ ged!(mut &editor).feedback =
+ Feedback::Error(format!("File '{file}' is already open"))
+ }
_ => ged!(mut &editor).feedback = Feedback::Error(format!("{err:?}")),
}
}
From 5de99a415e3b3666e9e296c082865ab6ddd8dbe2 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Tue, 3 Dec 2024 17:14:55 +0000
Subject: [PATCH 17/51] Added configuration options for file tree
---
config/.oxrc | 10 ++++++++++
src/config/colors.rs | 30 ++++++++++++++++++++++++++++--
src/config/filetree.rs | 39 +++++++++++++++++++++++++++++++++++++++
src/config/mod.rs | 9 +++++++++
src/editor/filetree.rs | 22 ++++++++++++++++------
src/editor/interface.rs | 16 ++++++++++------
src/main.rs | 4 ++--
7 files changed, 114 insertions(+), 16 deletions(-)
create mode 100644 src/config/filetree.rs
diff --git a/config/.oxrc b/config/.oxrc
index e162c703..6c1bf2a6 100644
--- a/config/.oxrc
+++ b/config/.oxrc
@@ -405,6 +405,11 @@ colors.error_bg = {41, 41, 61}
colors.selection_fg = {255, 255, 255}
colors.selection_bg = {59, 59, 130}
+colors.file_tree_bg = {41, 41, 61}
+colors.file_tree_fg = {255, 255, 255}
+colors.file_tree_selection_fg = {255, 255, 255}
+colors.file_tree_selection_bg = {59, 59, 130}
+
-- Configure Line Numbers --
line_numbers.enabled = true
line_numbers.padding_left = 1
@@ -414,6 +419,11 @@ line_numbers.padding_right = 1
terminal.mouse_enabled = true
terminal.scroll_amount = 4
+-- Configure File Tree --
+file_tree.width = 0.2
+file_tree.move_focus_to_file = true
+file_tree.icons = true
+
-- Configure Tab Line --
tab_line.enabled = true
tab_line.separators = true
diff --git a/src/config/colors.rs b/src/config/colors.rs
index 4f909abb..e020dae4 100644
--- a/src/config/colors.rs
+++ b/src/config/colors.rs
@@ -36,6 +36,11 @@ pub struct Colors {
pub selection_fg: Color,
pub selection_bg: Color,
+
+ pub file_tree_fg: Color,
+ pub file_tree_bg: Color,
+ pub file_tree_selection_fg: Color,
+ pub file_tree_selection_bg: Color,
}
impl Default for Colors {
@@ -67,8 +72,13 @@ impl Default for Colors {
error_bg: Color::Rgb(41, 41, 61),
error_fg: Color::Rgb(255, 100, 100),
- selection_fg: Color::Rgb(41, 41, 61),
- selection_bg: Color::Rgb(41, 41, 61),
+ selection_fg: Color::Rgb(255, 255, 255),
+ selection_bg: Color::Rgb(59, 59, 130),
+
+ file_tree_bg: Color::Rgb(41, 41, 61),
+ file_tree_fg: Color::Rgb(255, 255, 255),
+ file_tree_selection_bg: Color::Rgb(59, 59, 130),
+ file_tree_selection_fg: Color::Rgb(255, 255, 255),
}
}
}
@@ -197,6 +207,22 @@ impl LuaUserData for Colors {
this.selection_bg = Color::from_lua(value);
Ok(())
});
+ fields.add_field_method_set("file_tree_bg", |_, this, value| {
+ this.file_tree_bg = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_fg", |_, this, value| {
+ this.file_tree_fg = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_selection_bg", |_, this, value| {
+ this.file_tree_selection_bg = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_selection_fg", |_, this, value| {
+ this.file_tree_selection_fg = Color::from_lua(value);
+ Ok(())
+ });
}
}
diff --git a/src/config/filetree.rs b/src/config/filetree.rs
new file mode 100644
index 00000000..83778c85
--- /dev/null
+++ b/src/config/filetree.rs
@@ -0,0 +1,39 @@
+/// Related to file tree configuration options
+use mlua::prelude::*;
+
+#[derive(Debug)]
+pub struct FileTree {
+ pub width: f64,
+ pub move_focus_to_file: bool,
+ pub icons: bool,
+}
+
+impl Default for FileTree {
+ fn default() -> Self {
+ Self {
+ width: 0.2,
+ move_focus_to_file: true,
+ icons: false,
+ }
+ }
+}
+
+impl LuaUserData for FileTree {
+ fn add_fields>(fields: &mut F) {
+ fields.add_field_method_get("width", |_, this| Ok(this.width));
+ fields.add_field_method_set("width", |_, this, value| {
+ this.width = value;
+ Ok(())
+ });
+ fields.add_field_method_get("move_focus_to_file", |_, this| Ok(this.move_focus_to_file));
+ fields.add_field_method_set("move_focus_to_file", |_, this, value| {
+ this.move_focus_to_file = value;
+ Ok(())
+ });
+ fields.add_field_method_get("icons", |_, this| Ok(this.icons));
+ fields.add_field_method_set("icons", |_, this, value| {
+ this.icons = value;
+ Ok(())
+ });
+ }
+}
diff --git a/src/config/mod.rs b/src/config/mod.rs
index 380d8315..a92e3b4d 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -8,6 +8,7 @@ use std::sync::{Arc, Mutex};
mod assistant;
mod colors;
mod editor;
+mod filetree;
mod highlighting;
mod interface;
mod keys;
@@ -15,6 +16,7 @@ mod tasks;
pub use assistant::Assistant;
pub use colors::{Color, Colors};
+pub use filetree::FileTree;
pub use highlighting::SyntaxHighlighting;
pub use interface::{GreetingMessage, HelpMessage, LineNumbers, StatusLine, TabLine, Terminal};
pub use keys::{get_listeners, key_to_string, run_key, run_key_before};
@@ -82,6 +84,9 @@ macro_rules! config {
.borrow::<$crate::config::HelpMessage>()
.unwrap()
};
+ ($cfg:expr, file_tree) => {
+ $cfg.file_tree.borrow::<$crate::config::FileTree>().unwrap()
+ };
($cfg:expr, terminal) => {
$cfg.terminal.borrow::<$crate::config::Terminal>().unwrap()
};
@@ -97,6 +102,7 @@ pub struct Config {
pub tab_line: LuaAnyUserData,
pub greeting_message: LuaAnyUserData,
pub help_message: LuaAnyUserData,
+ pub file_tree: LuaAnyUserData,
pub terminal: LuaAnyUserData,
pub document: LuaAnyUserData,
pub task_manager: Arc>,
@@ -113,6 +119,7 @@ impl Config {
let colors = lua.create_userdata(Colors::default())?;
let status_line = lua.create_userdata(StatusLine::default())?;
let tab_line = lua.create_userdata(TabLine::default())?;
+ let file_tree = lua.create_userdata(FileTree::default())?;
let terminal = lua.create_userdata(Terminal::default())?;
let document = lua.create_userdata(Document::default())?;
@@ -132,6 +139,7 @@ impl Config {
lua.globals().set("help_message", help_message.clone())?;
lua.globals().set("status_line", status_line.clone())?;
lua.globals().set("tab_line", tab_line.clone())?;
+ lua.globals().set("file_tree", file_tree.clone())?;
lua.globals().set("colors", colors.clone())?;
lua.globals().set("terminal", terminal.clone())?;
lua.globals().set("document", document.clone())?;
@@ -178,6 +186,7 @@ impl Config {
tab_line,
greeting_message,
help_message,
+ file_tree,
terminal,
document,
task_manager,
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index 987aaa32..d394cb7b 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -1,6 +1,6 @@
/// Utilities for handling the file tree
use crate::editor::FileLayout;
-use crate::{Editor, OxError, Result};
+use crate::{config, Editor, OxError, Result};
use kaolinite::utils::{file_or_dir, get_cwd, get_file_name};
use std::path::{Path, PathBuf};
@@ -272,6 +272,10 @@ impl Editor {
#[allow(clippy::cast_precision_loss)]
pub fn open_file_tree(&mut self) {
if !self.file_tree_is_open() {
+ // Calculate display proportions
+ let width = config!(self.config, file_tree).width;
+ let other = if width <= 1.0 { 1.0 - width } else { 0.0 };
+ // Set up file tree values
self.old_ptr = self.ptr.clone();
if let Some(cwd) = get_cwd() {
if let Ok(ft) = FileTree::build(&cwd) {
@@ -282,7 +286,7 @@ impl Editor {
// Wrap existing file layout in new file layout
if let FileLayout::SideBySide(ref mut layouts) = &mut self.files {
// Shrink existing splits
- let redistribute = 0.2 / layouts.len() as f64;
+ let redistribute = width / layouts.len() as f64;
for (_, prop) in &mut *layouts {
if *prop >= redistribute {
*prop -= redistribute;
@@ -291,11 +295,11 @@ impl Editor {
}
}
// Insert file tree
- layouts.insert(0, (FileLayout::FileTree, 0.2));
+ layouts.insert(0, (FileLayout::FileTree, width));
} else {
self.files = FileLayout::SideBySide(vec![
- (FileLayout::FileTree, 0.2),
- (self.files.clone(), 0.8),
+ (FileLayout::FileTree, width),
+ (self.files.clone(), other),
]);
}
self.ptr = vec![0];
@@ -403,9 +407,11 @@ impl Editor {
/// Open a file from the file tree
pub fn file_tree_open_file(&mut self) -> Result<()> {
- // Default behaviour is open a file in the background and return to file tree
+ // Work out how to behave when opening files
+ let move_focus = config!(self.config, file_tree).move_focus_to_file;
if let Some(file_name) = &self.file_tree_selection.clone() {
// Restore to old pointer to open
+ let ptr_cache = self.ptr.clone();
let mut temp = self.old_ptr.clone();
temp.insert(0, 1);
self.ptr = temp;
@@ -413,6 +419,10 @@ impl Editor {
self.open(file_name)?;
self.next();
self.update_cwd();
+ // If we don't want to move focus, then move focus back to the file tree
+ if !move_focus {
+ self.ptr = ptr_cache;
+ }
}
Ok(())
}
diff --git a/src/editor/interface.rs b/src/editor/interface.rs
index c2f63550..190b90d8 100644
--- a/src/editor/interface.rs
+++ b/src/editor/interface.rs
@@ -547,10 +547,14 @@ impl Editor {
/// Render a line in the file tree
#[allow(clippy::similar_names)]
fn render_file_tree(&mut self, y: usize, length: usize) -> Result {
- let editor_bg = Bg(config!(self.config, colors).editor_bg.to_color()?);
- let editor_fg = Fg(config!(self.config, colors).editor_fg.to_color()?);
- let selection_bg = Bg(config!(self.config, colors).selection_bg.to_color()?);
- let selection_fg = Fg(config!(self.config, colors).selection_fg.to_color()?);
+ let ft_bg = Bg(config!(self.config, colors).file_tree_bg.to_color()?);
+ let ft_fg = Fg(config!(self.config, colors).file_tree_fg.to_color()?);
+ let ft_selection_bg = Bg(config!(self.config, colors)
+ .file_tree_selection_bg
+ .to_color()?);
+ let ft_selection_fg = Fg(config!(self.config, colors)
+ .file_tree_selection_fg
+ .to_color()?);
// Work out which line to use
let line = self
.render_cache
@@ -578,9 +582,9 @@ impl Editor {
result += &" ".repeat(available);
// Return result
if self.render_cache.file_tree_selection == Some(y) {
- Ok(format!("{selection_bg}{selection_fg}{result}"))
+ Ok(format!("{ft_selection_bg}{ft_selection_fg}{result}"))
} else {
- Ok(format!("{editor_bg}{editor_fg}{result}"))
+ Ok(format!("{ft_bg}{ft_fg}{result}"))
}
}
diff --git a/src/main.rs b/src/main.rs
index a1e0a430..79025a2c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -251,11 +251,11 @@ fn handle_event(editor: &AnyUserData, event: &CEvent, lua: &Lua) -> Result<()> {
// Nicely display error to user
match err {
OxError::Lua(err) => {
- handle_lua_error("event", Err(err), &mut ged!(mut &editor).feedback)
+ handle_lua_error("event", Err(err), &mut ged!(mut &editor).feedback);
}
OxError::AlreadyOpen { file } => {
ged!(mut &editor).feedback =
- Feedback::Error(format!("File '{file}' is already open"))
+ Feedback::Error(format!("File '{file}' is already open"));
}
_ => ged!(mut &editor).feedback = Feedback::Error(format!("{err:?}")),
}
From c21359a4fec968b324b4a5ab79d5a0c02a6ed446 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Tue, 3 Dec 2024 17:32:53 +0000
Subject: [PATCH 18/51] Added nice file tree colour schemes to all themes
---
plugins/themes/default16.lua | 5 +++++
plugins/themes/galaxy.lua | 5 +++++
plugins/themes/omni.lua | 5 +++++
plugins/themes/transparent.lua | 4 ++++
plugins/themes/tropical.lua | 5 +++++
5 files changed, 24 insertions(+)
diff --git a/plugins/themes/default16.lua b/plugins/themes/default16.lua
index 7058b2b9..8b686f2b 100644
--- a/plugins/themes/default16.lua
+++ b/plugins/themes/default16.lua
@@ -48,6 +48,11 @@ colors.error_fg = red
colors.selection_bg = darkgrey
colors.selection_fg = cyan
+colors.file_tree_bg = black
+colors.file_tree_fg = white
+colors.file_tree_selection_bg = darkgrey
+colors.file_tree_selection_fg = cyan
+
-- Configure Syntax Highlighting Colours --
syntax:set("string", green) -- Strings, bright green
syntax:set("comment", darkgrey) -- Comments, light purple/gray
diff --git a/plugins/themes/galaxy.lua b/plugins/themes/galaxy.lua
index 0b568f3b..fc8035d3 100644
--- a/plugins/themes/galaxy.lua
+++ b/plugins/themes/galaxy.lua
@@ -44,6 +44,11 @@ colors.error_fg = red
colors.selection_bg = grey1
colors.selection_fg = lightblue
+colors.file_tree_bg = black
+colors.file_tree_fg = white
+colors.file_tree_selection_bg = purple
+colors.file_tree_selection_fg = black
+
-- Configure Syntax Highlighting Colours --
syntax:set("string", green) -- Strings, bright green
syntax:set("comment", grey3) -- Comments, light purple/gray
diff --git a/plugins/themes/omni.lua b/plugins/themes/omni.lua
index 9ba09f4f..6f4244e4 100644
--- a/plugins/themes/omni.lua
+++ b/plugins/themes/omni.lua
@@ -44,6 +44,11 @@ colors.error_fg = red
colors.selection_bg = selection
colors.selection_fg = foreground
+colors.file_tree_bg = background
+colors.file_tree_fg = foreground
+colors.file_tree_selection_bg = pink
+colors.file_tree_selection_fg = background
+
-- Configure Syntax Highlighting Colours --
syntax:set("string", yellow) -- Strings, fresh green
syntax:set("comment", comment) -- Comments, muted and subtle
diff --git a/plugins/themes/transparent.lua b/plugins/themes/transparent.lua
index c67ec8ce..91961880 100644
--- a/plugins/themes/transparent.lua
+++ b/plugins/themes/transparent.lua
@@ -9,3 +9,7 @@ colors.split_fg = 15
colors.info_bg = 'transparent'
colors.warning_bg = 'transparent'
colors.error_bg = 'transparent'
+
+colors.file_tree_bg = 'transparent'
+colors.file_tree_selection_fg = {35, 240, 144}
+colors.file_tree_selection_bg = 'transparent'
diff --git a/plugins/themes/tropical.lua b/plugins/themes/tropical.lua
index aaa9b182..38c48cb9 100644
--- a/plugins/themes/tropical.lua
+++ b/plugins/themes/tropical.lua
@@ -44,6 +44,11 @@ colors.error_fg = red
colors.selection_bg = grey1
colors.selection_fg = lightblue
+colors.file_tree_bg = black
+colors.file_tree_fg = white
+colors.file_tree_selection_bg = lightblue
+colors.file_tree_selection_fg = black
+
-- Configure Syntax Highlighting Colours --
syntax:set("string", lightblue) -- Strings, bright green
syntax:set("comment", grey3) -- Comments, light purple/gray
From 953eeef69c9dc81ff9967330945969928afe976d Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Tue, 3 Dec 2024 18:03:17 +0000
Subject: [PATCH 19/51] Added language-specific icons
---
src/editor/filetree.rs | 34 ++++++++++++++++++++++------------
src/editor/filetypes.rs | 13 +++++++++++++
src/editor/interface.rs | 3 ++-
3 files changed, 37 insertions(+), 13 deletions(-)
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index d394cb7b..1802ca05 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -1,6 +1,6 @@
/// Utilities for handling the file tree
use crate::editor::FileLayout;
-use crate::{config, Editor, OxError, Result};
+use crate::{config, Editor, OxError, Result, FileTypes};
use kaolinite::utils::{file_or_dir, get_cwd, get_file_name};
use std::path::{Path, PathBuf};
@@ -173,8 +173,16 @@ impl FileTree {
get_file_name(path).is_some_and(|name| name.starts_with('.'))
}
+ /// Get a language-related icon for this node
+ fn lang_icon(&self, fts: &FileTypes) -> Option {
+ let path = match self {
+ Self::File { path } | Self::Dir { path, .. } => path,
+ };
+ fts.identify_from_path(path).map(|ft| ft.icon + " ")
+ }
+
/// Get the appropriate icon
- fn icon(&self) -> &str {
+ fn icon(&self, fts: &FileTypes) -> String {
let is_file = match self {
Self::File { .. } => true,
Self::Dir { .. } => false,
@@ -184,15 +192,17 @@ impl FileTree {
Self::Dir { files, .. } => files.is_some(),
};
let is_hidden = self.is_hidden();
- match (is_file, is_hidden, is_expanded) {
+ match (self.lang_icon(fts), is_file, is_hidden, is_expanded) {
+ // Language specific icons
+ (Some(icon), _, _, _) => icon,
// Closed folders
- (false, false, false) => " ",
- (false, true, false) => " ",
+ (_, false, false, false) => " ".to_string(),
+ (_, false, true, false) => " ".to_string(),
// Opened folders
- (false, _, true) => " ",
+ (_, false, _, true) => " ".to_string(),
// Files
- (true, false, _) => " ",
- (true, true, _) => " ",
+ (_, true, false, _) => " ".to_string(),
+ (_, true, true, _) => " ".to_string(),
}
}
@@ -204,12 +214,12 @@ impl FileTree {
}
/// Display this file tree
- pub fn display(&self, selection: &str) -> (Vec, Option) {
+ pub fn display(&self, selection: &str, fts: &FileTypes) -> (Vec, Option) {
match self {
Self::File { path } => (
vec![format!(
"{}{}",
- self.icon(),
+ self.icon(fts),
get_file_name(path).unwrap_or(path.to_string())
)],
if self.is_selected(selection) {
@@ -224,7 +234,7 @@ impl FileTree {
// Write self
result.push(format!(
"{}{}",
- self.icon(),
+ self.icon(fts),
get_file_name(path).unwrap_or(path.to_string())
));
if self.is_selected(selection) {
@@ -233,7 +243,7 @@ impl FileTree {
// Write child nodes
if let Some(files) = files {
for file in files {
- let (sub_display, sub_at) = file.display(selection);
+ let (sub_display, sub_at) = file.display(selection, fts);
for (c, s) in sub_display.iter().enumerate() {
result.push(format!(" {s}"));
if let Some(sub_at) = sub_at {
diff --git a/src/editor/filetypes.rs b/src/editor/filetypes.rs
index 98ca4011..9e440b54 100644
--- a/src/editor/filetypes.rs
+++ b/src/editor/filetypes.rs
@@ -33,6 +33,19 @@ impl FileTypes {
None
}
+ pub fn identify_from_path(&self, path: &str) -> Option {
+ if let Some(e) = Path::new(&path).extension() {
+ let file_name = get_file_name(path).unwrap_or_default();
+ let extension = e.to_str().unwrap_or_default().to_string();
+ for t in &self.types {
+ if t.fits(&extension, &file_name, "") {
+ return Some(t.clone());
+ }
+ }
+ }
+ None
+ }
+
pub fn get_name(&self, name: &str) -> Option {
self.types.iter().find(|t| t.name == name).cloned()
}
diff --git a/src/editor/interface.rs b/src/editor/interface.rs
index 190b90d8..b85657e1 100644
--- a/src/editor/interface.rs
+++ b/src/editor/interface.rs
@@ -54,9 +54,10 @@ impl Editor {
let help_end = help_start + help_length;
self.render_cache.help_message_span = help_start..help_end + 1;
// Calculate file tree display representation
+ let fts = &config!(self.config, document).file_types;
if let Some(file_tree) = self.file_tree.as_ref() {
let (files, sel) =
- file_tree.display(self.file_tree_selection.as_ref().unwrap_or(&String::new()));
+ file_tree.display(self.file_tree_selection.as_ref().unwrap_or(&String::new()), fts);
self.render_cache.file_tree = files;
self.render_cache.file_tree_selection = sel;
}
From 50d0259461747ad37d85b84c8c04493574749a08 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Tue, 3 Dec 2024 18:14:58 +0000
Subject: [PATCH 20/51] Configuration option for icons and language icons
---
config/.oxrc | 3 ++-
src/config/filetree.rs | 7 +++++++
src/editor/filetree.rs | 46 +++++++++++++++++++++++++----------------
src/editor/interface.rs | 8 +++++--
4 files changed, 43 insertions(+), 21 deletions(-)
diff --git a/config/.oxrc b/config/.oxrc
index 6c1bf2a6..a8add456 100644
--- a/config/.oxrc
+++ b/config/.oxrc
@@ -422,7 +422,8 @@ terminal.scroll_amount = 4
-- Configure File Tree --
file_tree.width = 0.2
file_tree.move_focus_to_file = true
-file_tree.icons = true
+file_tree.icons = false
+file_tree.language_icons = true
-- Configure Tab Line --
tab_line.enabled = true
diff --git a/src/config/filetree.rs b/src/config/filetree.rs
index 83778c85..6401d2f9 100644
--- a/src/config/filetree.rs
+++ b/src/config/filetree.rs
@@ -6,6 +6,7 @@ pub struct FileTree {
pub width: f64,
pub move_focus_to_file: bool,
pub icons: bool,
+ pub language_icons: bool,
}
impl Default for FileTree {
@@ -14,6 +15,7 @@ impl Default for FileTree {
width: 0.2,
move_focus_to_file: true,
icons: false,
+ language_icons: true,
}
}
}
@@ -35,5 +37,10 @@ impl LuaUserData for FileTree {
this.icons = value;
Ok(())
});
+ fields.add_field_method_get("language_icons", |_, this| Ok(this.language_icons));
+ fields.add_field_method_set("language_icons", |_, this, value| {
+ this.language_icons = value;
+ Ok(())
+ });
}
}
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index 1802ca05..0806de4f 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -1,6 +1,7 @@
+use crate::config::FileTree as CfgFT;
/// Utilities for handling the file tree
use crate::editor::FileLayout;
-use crate::{config, Editor, OxError, Result, FileTypes};
+use crate::{config, Editor, FileTypes, OxError, Result};
use kaolinite::utils::{file_or_dir, get_cwd, get_file_name};
use std::path::{Path, PathBuf};
@@ -174,15 +175,19 @@ impl FileTree {
}
/// Get a language-related icon for this node
- fn lang_icon(&self, fts: &FileTypes) -> Option {
- let path = match self {
- Self::File { path } | Self::Dir { path, .. } => path,
- };
- fts.identify_from_path(path).map(|ft| ft.icon + " ")
+ fn lang_icon(&self, fts: &FileTypes, config: &CfgFT) -> Option {
+ if config.language_icons {
+ let path = match self {
+ Self::File { path } | Self::Dir { path, .. } => path,
+ };
+ fts.identify_from_path(path).map(|ft| ft.icon + " ")
+ } else {
+ None
+ }
}
/// Get the appropriate icon
- fn icon(&self, fts: &FileTypes) -> String {
+ fn icon(&self, fts: &FileTypes, config: &CfgFT) -> String {
let is_file = match self {
Self::File { .. } => true,
Self::Dir { .. } => false,
@@ -192,7 +197,7 @@ impl FileTree {
Self::Dir { files, .. } => files.is_some(),
};
let is_hidden = self.is_hidden();
- match (self.lang_icon(fts), is_file, is_hidden, is_expanded) {
+ match (self.lang_icon(fts, config), is_file, is_hidden, is_expanded) {
// Language specific icons
(Some(icon), _, _, _) => icon,
// Closed folders
@@ -214,19 +219,20 @@ impl FileTree {
}
/// Display this file tree
- pub fn display(&self, selection: &str, fts: &FileTypes) -> (Vec, Option) {
+ pub fn display(&self, sel: &str, fts: &FileTypes, cfg: &CfgFT) -> (Vec, Option) {
+ let icons = cfg.icons;
match self {
Self::File { path } => (
vec![format!(
"{}{}",
- self.icon(fts),
+ if icons {
+ self.icon(fts, cfg)
+ } else {
+ String::new()
+ },
get_file_name(path).unwrap_or(path.to_string())
)],
- if self.is_selected(selection) {
- Some(0)
- } else {
- None
- },
+ if self.is_selected(sel) { Some(0) } else { None },
),
Self::Dir { path, files } => {
let mut result = vec![];
@@ -234,16 +240,20 @@ impl FileTree {
// Write self
result.push(format!(
"{}{}",
- self.icon(fts),
+ if icons {
+ self.icon(fts, cfg)
+ } else {
+ String::new()
+ },
get_file_name(path).unwrap_or(path.to_string())
));
- if self.is_selected(selection) {
+ if self.is_selected(sel) {
at = Some(result.len().saturating_sub(1));
}
// Write child nodes
if let Some(files) = files {
for file in files {
- let (sub_display, sub_at) = file.display(selection, fts);
+ let (sub_display, sub_at) = file.display(sel, fts, cfg);
for (c, s) in sub_display.iter().enumerate() {
result.push(format!(" {s}"));
if let Some(sub_at) = sub_at {
diff --git a/src/editor/interface.rs b/src/editor/interface.rs
index b85657e1..cd667336 100644
--- a/src/editor/interface.rs
+++ b/src/editor/interface.rs
@@ -55,9 +55,13 @@ impl Editor {
self.render_cache.help_message_span = help_start..help_end + 1;
// Calculate file tree display representation
let fts = &config!(self.config, document).file_types;
+ let ft_config = &config!(self.config, file_tree);
if let Some(file_tree) = self.file_tree.as_ref() {
- let (files, sel) =
- file_tree.display(self.file_tree_selection.as_ref().unwrap_or(&String::new()), fts);
+ let (files, sel) = file_tree.display(
+ self.file_tree_selection.as_ref().unwrap_or(&String::new()),
+ fts,
+ ft_config,
+ );
self.render_cache.file_tree = files;
self.render_cache.file_tree_selection = sel;
}
From 2ef35469d5f7e85890d130e6417dfaab13b6a738 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Tue, 3 Dec 2024 18:46:22 +0000
Subject: [PATCH 21/51] Added file tree icon options to configuration assistant
---
src/config/assistant.rs | 55 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 55 insertions(+)
diff --git a/src/config/assistant.rs b/src/config/assistant.rs
index 2ff3565b..f5494ab0 100644
--- a/src/config/assistant.rs
+++ b/src/config/assistant.rs
@@ -176,6 +176,8 @@ pub struct Assistant {
pub tab_line: bool,
pub tab_line_sep: bool,
pub greeting_message: bool,
+ pub file_tree_icons: bool,
+ pub file_tree_language_icons: bool,
pub plugins: Vec,
}
@@ -195,6 +197,9 @@ impl Default for Assistant {
tab_line_sep: true,
// Greeting Message
greeting_message: true,
+ // File Tree
+ file_tree_icons: false,
+ file_tree_language_icons: true,
// Mouse and Cursor Behaviour
mouse: true,
scroll_sensitivity: 4,
@@ -229,6 +234,8 @@ impl Assistant {
Self::ask_mouse_cursor(&mut result)?;
// Icons
Self::ask_icons(&mut result)?;
+ // File tree
+ Self::ask_file_tree(&mut result)?;
// Plug-Ins
Self::ask_plugins(&mut result)?;
// Create the configuration file (and print it)
@@ -456,6 +463,28 @@ impl Assistant {
Ok(())
}
+ pub fn ask_file_tree(result: &mut Self) -> Result<()> {
+ if result.icons {
+ let orange = Fg(Color::Ansi(202).to_color()?);
+ let yellow = Fg(Color::Ansi(220).to_color()?);
+ let green = Fg(Color::Ansi(34).to_color()?);
+ let reset = Fg(Color::Transparent.to_color()?);
+ println!("🖹 file1.txt \n🖹 file2.txt \n🖹 file3.txt \n");
+ result.file_tree_icons =
+ Self::confirmation("Would you like icons in the file tree?", false);
+ if result.file_tree_icons {
+ println!(
+ "\n {green}🎵{reset} file1.mp3 \n {orange}{{}}{reset} file2.css \n {yellow}>{reset} file3.html \n"
+ );
+ result.file_tree_language_icons = Self::confirmation(
+ "Would you like the tab line to have language specific icons?",
+ true,
+ );
+ }
+ }
+ Ok(())
+ }
+
pub fn ask_line_numbers(result: &mut Self) -> Result<()> {
let red = Fg(Color::Ansi(196).to_color()?);
let orange = Fg(Color::Ansi(202).to_color()?);
@@ -690,6 +719,7 @@ impl Assistant {
}
/// Turn the configuration assistant details into a lua file
+ #[allow(clippy::too_many_lines)]
pub fn to_config(&self) -> String {
let mut result = String::new();
let (sections, fields) = self.diff();
@@ -779,6 +809,19 @@ impl Assistant {
result += "\n-- Greeting Message Configuration --\n";
result += &format!("greeting_message.enabled = {}\n", self.greeting_message);
}
+ // Configuration of file tree
+ if sections.contains(&"file_tree") {
+ result += "\n-- File Tree Configuration --\n";
+ if fields.contains(&"file_tree_icons") {
+ result += &format!("file_tree.icons = {}\n", self.file_tree_icons);
+ }
+ if fields.contains(&"file_tree_language_icons") {
+ result += &format!(
+ "file_tree.language_icons = {}\n",
+ self.file_tree_language_icons
+ );
+ }
+ }
// Configuration of mouse and cursor behaviour
if sections.contains(&"cursors") {
result += "\n-- Cursor Configuration --\n";
@@ -820,6 +863,14 @@ impl Assistant {
("mouse", self.mouse != def.mouse),
("cursor_wrap", self.cursor_wrap != def.cursor_wrap),
("icons", self.icons != def.icons),
+ (
+ "file_tree_icons",
+ self.file_tree_icons != def.file_tree_icons,
+ ),
+ (
+ "file_tree_language_icons",
+ self.file_tree_language_icons != def.file_tree_language_icons,
+ ),
(
"line_number_padding",
self.line_number_padding != def.line_number_padding,
@@ -865,6 +916,10 @@ impl Assistant {
|| fields.contains(&"scroll_sensitivity")
|| fields.contains(&"cursor_wrap"),
),
+ (
+ "file_tree",
+ fields.contains(&"file_tree_icons") || fields.contains(&"file_tree_language_icons"),
+ ),
];
let sections = sections
.iter()
From d1db3113fb8c4d40b36596828f1000c55199f626 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Tue, 3 Dec 2024 20:52:46 +0000
Subject: [PATCH 22/51] Added colours to file types and file tree
---
plugins/todo.lua | 3 +-
src/config/colors.rs | 4 +-
src/config/mod.rs | 2 +
src/editor/filetree.rs | 76 ++++++++++++++-------------
src/editor/filetypes.rs | 7 ++-
src/editor/interface.rs | 47 +++++++----------
src/editor/mod.rs | 2 +-
src/plugin/bootstrap.lua | 109 +++++++++++++++++++++++++++++++++++++++
8 files changed, 182 insertions(+), 68 deletions(-)
diff --git a/plugins/todo.lua b/plugins/todo.lua
index 207dada1..eaf901a6 100644
--- a/plugins/todo.lua
+++ b/plugins/todo.lua
@@ -1,5 +1,5 @@
--[[
-Todo Lists v0.2
+Todo Lists v0.3
This plug-in will provide todo list functionality on files with the extension .todo
You can mark todos as done / not done by using the Ctrl + Enter key combination
@@ -12,6 +12,7 @@ file_types["Todo"] = {
extensions = {"todo"},
files = {".todo.md", ".todo"},
modelines = {},
+ color = 83,
}
-- Add syntax highlighting to .todo files (done todos are comments)
diff --git a/src/config/colors.rs b/src/config/colors.rs
index e020dae4..bc8e6e0d 100644
--- a/src/config/colors.rs
+++ b/src/config/colors.rs
@@ -6,7 +6,7 @@ use mlua::prelude::*;
use super::issue_warning;
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct Colors {
pub editor_bg: Color,
pub editor_fg: Color,
@@ -226,7 +226,7 @@ impl LuaUserData for Colors {
}
}
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub enum Color {
Rgb(u8, u8, u8),
Hex(String),
diff --git a/src/config/mod.rs b/src/config/mod.rs
index a92e3b4d..c3d5af82 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -376,12 +376,14 @@ impl FromLua for FileTypes {
.pairs::()
.filter_map(|val| if let Ok((_, v)) = val { Some(v) } else { None })
.collect::>();
+ let color = Color::from_lua(info.get("color")?);
result.push(FileType {
name,
icon,
files,
extensions,
modelines,
+ color,
});
}
}
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index 0806de4f..74866a10 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -1,10 +1,15 @@
-use crate::config::FileTree as CfgFT;
/// Utilities for handling the file tree
+
+use crate::config::{FileTree as CfgFT, Color};
use crate::editor::FileLayout;
use crate::{config, Editor, FileTypes, OxError, Result};
use kaolinite::utils::{file_or_dir, get_cwd, get_file_name};
use std::path::{Path, PathBuf};
+/// How parts of a file tree are stored
+/// (Padding, Icon, Icon Color, File Name)
+pub type FTParts = Vec<(usize, String, Option, String)>;
+
/// The backend of a file tree - stores the structure of the files and directories
#[derive(Debug)]
pub enum FileTree {
@@ -175,19 +180,20 @@ impl FileTree {
}
/// Get a language-related icon for this node
- fn lang_icon(&self, fts: &FileTypes, config: &CfgFT) -> Option {
+ fn lang_icon(&self, fts: &FileTypes, config: &CfgFT) -> Option<(String, Color)> {
if config.language_icons {
let path = match self {
Self::File { path } | Self::Dir { path, .. } => path,
};
- fts.identify_from_path(path).map(|ft| ft.icon + " ")
- } else {
- None
+ if let Some(ft) = fts.identify_from_path(path) {
+ return Some((ft.icon, ft.color));
+ }
}
+ None
}
/// Get the appropriate icon
- fn icon(&self, fts: &FileTypes, config: &CfgFT) -> String {
+ fn icon(&self, fts: &FileTypes, config: &CfgFT) -> (String, Option) {
let is_file = match self {
Self::File { .. } => true,
Self::Dir { .. } => false,
@@ -199,15 +205,15 @@ impl FileTree {
let is_hidden = self.is_hidden();
match (self.lang_icon(fts, config), is_file, is_hidden, is_expanded) {
// Language specific icons
- (Some(icon), _, _, _) => icon,
+ (Some((icon, colour)), _, _, _) => (icon + " ", Some(colour)),
// Closed folders
- (_, false, false, false) => " ".to_string(),
- (_, false, true, false) => " ".to_string(),
+ (_, false, false, false) => (" ".to_string(), None),
+ (_, false, true, false) => (" ".to_string(), None),
// Opened folders
- (_, false, _, true) => " ".to_string(),
+ (_, false, _, true) => (" ".to_string(), None),
// Files
- (_, true, false, _) => " ".to_string(),
- (_, true, true, _) => " ".to_string(),
+ (_, true, false, _) => (" ".to_string(), None),
+ (_, true, true, _) => (" ".to_string(), None),
}
}
@@ -219,34 +225,32 @@ impl FileTree {
}
/// Display this file tree
- pub fn display(&self, sel: &str, fts: &FileTypes, cfg: &CfgFT) -> (Vec, Option) {
+ pub fn display(&self, sel: &str, fts: &FileTypes, cfg: &CfgFT) -> (FTParts, Option) {
let icons = cfg.icons;
match self {
- Self::File { path } => (
- vec![format!(
- "{}{}",
- if icons {
- self.icon(fts, cfg)
- } else {
- String::new()
- },
- get_file_name(path).unwrap_or(path.to_string())
- )],
- if self.is_selected(sel) { Some(0) } else { None },
- ),
+ Self::File { path } => {
+ let (icon, icon_color) = if icons {
+ self.icon(fts, cfg)
+ } else {
+ (String::new(), None)
+ };
+ let file_name = get_file_name(path).unwrap_or(path.to_string());
+ (
+ vec![(0, icon, icon_color, file_name)],
+ if self.is_selected(sel) { Some(0) } else { None },
+ )
+ }
Self::Dir { path, files } => {
let mut result = vec![];
let mut at = None;
// Write self
- result.push(format!(
- "{}{}",
- if icons {
- self.icon(fts, cfg)
- } else {
- String::new()
- },
- get_file_name(path).unwrap_or(path.to_string())
- ));
+ let (icon, icon_color) = if icons {
+ self.icon(fts, cfg)
+ } else {
+ (String::new(), None)
+ };
+ let file_name = get_file_name(path).unwrap_or(path.to_string());
+ result.push((0, icon, icon_color, file_name));
if self.is_selected(sel) {
at = Some(result.len().saturating_sub(1));
}
@@ -255,7 +259,9 @@ impl FileTree {
for file in files {
let (sub_display, sub_at) = file.display(sel, fts, cfg);
for (c, s) in sub_display.iter().enumerate() {
- result.push(format!(" {s}"));
+ let mut s = s.clone();
+ s.0 += 1;
+ result.push(s);
if let Some(sub_at) = sub_at {
if sub_at == c {
at = Some(result.len().saturating_sub(1));
diff --git a/src/editor/filetypes.rs b/src/editor/filetypes.rs
index 9e440b54..abda5450 100644
--- a/src/editor/filetypes.rs
+++ b/src/editor/filetypes.rs
@@ -1,5 +1,7 @@
-use crate::config;
/// Tools for managing and identifying file types
+
+use crate::config;
+use crate::config::Color;
use crate::editor::Config;
use kaolinite::utils::get_file_name;
use kaolinite::Document;
@@ -64,6 +66,8 @@ pub struct FileType {
pub extensions: Vec,
/// The modelines that files of this type have
pub modelines: Vec,
+ /// The colour associated with this file type
+ pub color: Color,
}
impl Default for FileType {
@@ -74,6 +78,7 @@ impl Default for FileType {
files: vec![],
extensions: vec![],
modelines: vec![],
+ color: Color::Transparent,
}
}
}
diff --git a/src/editor/interface.rs b/src/editor/interface.rs
index cd667336..f42e880b 100644
--- a/src/editor/interface.rs
+++ b/src/editor/interface.rs
@@ -1,6 +1,6 @@
use crate::config::SyntaxHighlighting as SH;
/// Functions for rendering the UI
-use crate::editor::FileLayout;
+use crate::editor::{FileLayout, FTParts};
use crate::error::{OxError, Result};
use crate::events::wait_for_event_hog;
use crate::ui::{key_event, size, Feedback};
@@ -24,7 +24,7 @@ pub struct RenderCache {
pub help_message: Vec<(bool, String)>,
pub help_message_width: usize,
pub help_message_span: Range,
- pub file_tree: Vec,
+ pub file_tree: FTParts,
pub file_tree_selection: Option,
}
@@ -552,6 +552,7 @@ impl Editor {
/// Render a line in the file tree
#[allow(clippy::similar_names)]
fn render_file_tree(&mut self, y: usize, length: usize) -> Result {
+ let selected = self.render_cache.file_tree_selection == Some(y);
let ft_bg = Bg(config!(self.config, colors).file_tree_bg.to_color()?);
let ft_fg = Fg(config!(self.config, colors).file_tree_fg.to_color()?);
let ft_selection_bg = Bg(config!(self.config, colors)
@@ -560,36 +561,26 @@ impl Editor {
let ft_selection_fg = Fg(config!(self.config, colors)
.file_tree_selection_fg
.to_color()?);
- // Work out which line to use
- let line = self
- .render_cache
- .file_tree
- .get(y)
- .map(std::string::ToString::to_string)
- .unwrap_or_default();
- let mut line = line.chars();
- // Trim to fit
- let mut result = String::new();
- let mut available = length;
- loop {
- if available > 0 {
- if let Some(next) = line.next() {
- let ch = width_char(&next, 4);
- available = available.saturating_sub(ch);
- result.push(next);
- } else {
- break;
- }
+ // Perform the rendering
+ let mut total_length = 0;
+ let line = self.render_cache.file_tree.get(y);
+ let mut line = if let Some((padding, icon, icon_colour, name)) = line {
+ total_length = padding * 2 + width(icon, 4) + width(name, 4);
+ if let (Some(colour), false) = (icon_colour, selected) {
+ let colour = Fg(colour.to_color()?);
+ format!("{}{colour}{icon}{ft_fg}{name}", " ".repeat(*padding))
} else {
- break;
+ format!("{}{icon}{name}", " ".repeat(*padding))
}
- }
- result += &" ".repeat(available);
+ } else {
+ String::new()
+ };
+ line += &" ".repeat(length.saturating_sub(total_length));
// Return result
- if self.render_cache.file_tree_selection == Some(y) {
- Ok(format!("{ft_selection_bg}{ft_selection_fg}{result}"))
+ if selected {
+ Ok(format!("{ft_selection_bg}{ft_selection_fg}{line}"))
} else {
- Ok(format!("{ft_bg}{ft_fg}{result}"))
+ Ok(format!("{ft_bg}{ft_fg}{line}"))
}
}
diff --git a/src/editor/mod.rs b/src/editor/mod.rs
index 06f7e7a3..a85524ed 100644
--- a/src/editor/mod.rs
+++ b/src/editor/mod.rs
@@ -28,7 +28,7 @@ mod scanning;
pub use cursor::{allowed_by_multi_cursor, handle_multiple_cursors};
pub use documents::{FileContainer, FileLayout};
-pub use filetree::FileTree;
+pub use filetree::{FileTree, FTParts};
pub use filetypes::{FileType, FileTypes};
pub use interface::RenderCache;
pub use macros::MacroMan;
diff --git a/src/plugin/bootstrap.lua b/src/plugin/bootstrap.lua
index 7b280abb..b690ed8e 100644
--- a/src/plugin/bootstrap.lua
+++ b/src/plugin/bootstrap.lua
@@ -165,522 +165,609 @@ file_types = {
files = {},
extensions = {"abap"},
modelines = {},
+ color = 21,
},
["Ada"] = {
icon = "",
files = {},
extensions = {"ada"},
modelines = {},
+ color = 28,
},
["AutoHotkey"] = {
icon = " ",
files = {},
extensions = {"ahk", "ahkl"},
modelines = {},
+ color = 157,
},
["AppleScript"] = {
icon = "",
files = {},
extensions = {"applescript", "scpt"},
modelines = {},
+ color = 252,
},
["Arc"] = {
icon = " ",
files = {},
extensions = {"arc"},
modelines = {},
+ color = 125,
},
["ASP"] = {
icon = " ",
files = {},
extensions = {"asp", "asax", "ascx", "ashx", "asmx", "aspx", "axd"},
modelines = {},
+ color = 33,
},
["ActionScript"] = {
icon = " ",
files = {},
extensions = {"as"},
modelines = {},
+ color = 202,
},
["AGS Script"] = {
icon = " ",
files = {},
extensions = {"asc", "ash"},
modelines = {},
+ color = 69,
},
["Assembly"] = {
icon = " ",
files = {},
extensions = {"asm", "nasm"},
modelines = {},
+ color = 250,
},
["Awk"] = {
icon = " ",
files = {},
extensions = {"awk", "auk", "gawk", "mawk", "nawk"},
modelines = {"#!\\s*/usr/bin/(env )?awk"},
+ color = 160,
},
["Batch"] = {
icon = " ",
files = {},
extensions = {"bat", "cmd"},
modelines = {},
+ color = 250,
},
["Brainfuck"] = {
icon = " ",
files = {},
extensions = {"b", "bf"},
modelines = {},
+ color = 226,
},
["C"] = {
icon = " ",
files = {},
extensions = {"c"},
modelines = {},
+ color = 33,
},
["CMake"] = {
icon = " ",
files = {},
extensions = {"cmake"},
modelines = {},
+ color = 40,
},
["Cobol"] = {
icon = " ",
files = {},
extensions = {"cbl", "cobol", "cob"},
modelines = {},
+ color = 57,
},
["Java"] = {
icon = " ",
files = {},
extensions = {"class", "java"},
modelines = {},
+ color = 202,
},
["Clojure"] = {
icon = " ",
files = {},
extensions = {"clj", "cl2", "cljs", "cljx", "cljc"},
modelines = {},
+ color = 37,
},
["CoffeeScript"] = {
icon = " ",
files = {},
extensions = {"coffee"},
modelines = {},
+ color = 130,
},
["Crystal"] = {
icon = " ",
files = {},
extensions = {"cr"},
modelines = {},
+ color = 233,
},
["Cuda"] = {
icon = " ",
files = {},
extensions = {"cu", "cuh"},
modelines = {},
+ color = 76,
},
["C++"] = {
icon = " ",
files = {},
extensions = {"cpp", "cxx"},
modelines = {},
+ color = 26,
},
["C#"] = {
icon = " ",
files = {},
extensions = {"cs", "cshtml", "csx"},
modelines = {},
+ color = 63,
},
["CSS"] = {
icon = " ",
files = {},
extensions = {"css"},
modelines = {},
+ color = 99,
},
["CSV"] = {
icon = " ",
files = {},
extensions = {"csv"},
modelines = {},
+ color = 248,
},
["D"] = {
icon = " ",
files = {},
extensions = {"d", "di"},
modelines = {},
+ color = 197,
},
["Dart"] = {
icon = " ",
files = {},
extensions = {"dart"},
modelines = {},
+ color = 33,
},
["Diff"] = {
icon = " ",
files = {},
extensions = {"diff", "patch"},
modelines = {},
+ color = 28,
},
["Dockerfile"] = {
icon = " ",
files = {},
extensions = {"dockerfile"},
modelines = {},
+ color = 33,
},
["Elixir"] = {
icon = " ",
files = {},
extensions = {"ex", "exs"},
modelines = {},
+ color = 56,
},
["Elm"] = {
icon = " ",
files = {},
extensions = {"elm"},
modelines = {},
+ color = 26,
},
["Emacs Lisp"] = {
icon = " ",
files = {},
extensions = {"el"},
modelines = {},
+ color = 63,
},
["ERB"] = {
icon = " ",
files = {},
extensions = {"erb"},
modelines = {},
+ color = 196,
},
["Erlang"] = {
icon = " ",
files = {},
extensions = {"erl", "es"},
modelines = {},
+ color = 196,
},
["F#"] = {
icon = " ",
files = {},
extensions = {"fs", "fsi", "fsx"},
modelines = {},
+ color = 39,
},
["FORTRAN"] = {
icon = " ",
files = {},
extensions = {"f", "f90", "fpp", "for"},
modelines = {},
+ color = 129,
},
["Fish"] = {
icon = " ",
files = {},
extensions = {"fish"},
modelines = {"#!\\s*/usr/bin/(env )?fish"},
+ color = 203,
},
["Forth"] = {
icon = " ",
files = {},
extensions = {"fth"},
modelines = {},
+ color = 196,
},
["ANTLR"] = {
icon = " ",
files = {},
extensions = {"g4"},
modelines = {},
+ color = 196,
},
["GDScript"] = {
icon = " ",
files = {},
extensions = {"gd"},
modelines = {},
+ color = 27,
},
["GLSL"] = {
icon = " ",
files = {},
extensions = {"glsl", "vert", "shader", "geo", "fshader", "vrx", "vsh", "vshader", "frag"},
modelines = {},
+ color = 33,
},
["Gnuplot"] = {
icon = " ",
files = {},
extensions = {"gnu", "gp", "plot"},
modelines = {},
+ color = 249,
},
["Go"] = {
icon = "",
files = {},
extensions = {"go"},
modelines = {},
+ color = 33,
},
["Groovy"] = {
icon = " ",
files = {},
extensions = {"groovy", "gvy"},
modelines = {},
+ color = 39,
},
["HLSL"] = {
icon = " ",
files = {},
extensions = {"hlsl"},
modelines = {},
+ color = 21,
},
["C Header"] = {
icon = " ",
files = {},
extensions = {"h"},
modelines = {},
+ color = 33,
},
["Haml"] = {
icon = "",
files = {},
extensions = {"haml"},
modelines = {},
+ color = 228,
},
["Handlebars"] = {
icon = " ",
files = {},
extensions = {"handlebars", "hbs"},
modelines = {},
+ color = 94,
},
["Haskell"] = {
icon = " ",
files = {},
extensions = {"hs"},
modelines = {},
+ color = 93,
},
["C++ Header"] = {
icon = " ",
files = {},
extensions = {"hpp"},
modelines = {},
+ color = 26,
},
["HTML"] = {
icon = " ",
files = {},
extensions = {"html", "htm", "xhtml"},
modelines = {},
+ color = 208,
},
["INI"] = {
icon = " ",
files = {},
extensions = {"ini", "cfg"},
modelines = {},
+ color = 248,
},
["Arduino"] = {
icon = " ",
files = {},
extensions = {"ino"},
modelines = {},
+ color = 75,
},
["J"] = {
icon = " ",
files = {},
extensions = {"ijs"},
modelines = {},
+ color = 45,
},
["JSON"] = {
icon = " ",
files = {},
extensions = {"json"},
modelines = {},
+ color = 247,
},
["JSX"] = {
icon = " ",
files = {},
extensions = {"jsx"},
modelines = {},
+ color = 33,
},
["JavaScript"] = {
icon = " ",
files = {},
extensions = {"js"},
modelines = {"#!\\s*/usr/bin/(env )?node"},
+ color = 220,
},
["Julia"] = {
icon = " ",
files = {},
extensions = {"jl"},
modelines = {},
+ color = 27,
},
["Kotlin"] = {
icon = " ",
files = {},
extensions = {"kt", "ktm", "kts"},
modelines = {},
+ color = 129,
},
["LLVM"] = {
icon = " ",
files = {},
extensions = {"ll"},
modelines = {},
+ color = 39,
},
["Lex"] = {
icon = " ",
files = {},
extensions = {"l", "lex"},
modelines = {},
+ color = 249,
},
["Lua"] = {
icon = " ",
files = {".oxrc"},
extensions = {"lua"},
modelines = {"#!\\s*/usr/bin/(env )?lua"},
+ color = 27,
},
["LiveScript"] = {
icon = " ",
files = {},
extensions = {"ls"},
modelines = {},
+ color = 39,
},
["LOLCODE"] = {
icon = " ",
files = {},
extensions = {"lol"},
modelines = {},
+ color = 160,
},
["Common Lisp"] = {
icon = " ",
files = {},
extensions = {"lisp", "asd", "lsp"},
modelines = {},
+ color = 243,
},
["Log file"] = {
icon = " ",
files = {},
extensions = {"log"},
modelines = {},
+ color = 248,
},
["M4"] = {
icon = " ",
files = {},
extensions = {"m4"},
modelines = {},
+ color = 21,
},
["Groff"] = {
icon = " ",
files = {},
extensions = {"man", "roff"},
modelines = {},
+ color = 249,
},
["Matlab"] = {
icon = " ",
files = {},
extensions = {"matlab"},
modelines = {},
+ color = 202,
},
["Objective-C"] = {
icon = " ",
files = {},
extensions = {"m"},
modelines = {},
+ color = 27,
},
["OCaml"] = {
icon = " ",
files = {},
extensions = {"ml"},
modelines = {},
+ color = 208,
},
["Makefile"] = {
icon = " ",
files = {"Makefile"},
extensions = {"mk", "mak"},
modelines = {},
+ color = 249,
},
["Markdown"] = {
icon = " ",
files = {},
extensions = {"md", "markdown"},
modelines = {},
+ color = 234,
},
["Nix"] = {
icon = " ",
files = {},
extensions = {"nix"},
modelines = {},
+ color = 33,
},
["NumPy"] = {
icon = " ",
files = {},
extensions = {"numpy"},
modelines = {},
+ color = 27,
},
["OpenCL"] = {
icon = " ",
files = {},
extensions = {"opencl", "cl"},
modelines = {},
+ color = 76,
},
["PHP"] = {
icon = " ",
files = {},
extensions = {"php"},
modelines = {"#!\\s*/usr/bin/(env )?php"},
+ color = 69,
},
["Pascal"] = {
icon = " ",
files = {},
extensions = {"pas"},
modelines = {},
+ color = 21,
},
["Perl"] = {
icon = " ",
files = {},
extensions = {"pl"},
modelines = {"#!\\s*/usr/bin/(env )?perl"},
+ color = 39,
},
["PowerShell"] = {
icon = " ",
files = {},
extensions = {"psl"},
modelines = {},
+ color = 33,
},
["Prolog"] = {
icon = " ",
files = {},
extensions = {"pro"},
modelines = {},
+ color = 202,
},
["Python"] = {
icon = " ",
files = {},
extensions = {"py", "pyw"},
modelines = {"#!\\s*/usr/bin/(env )?python3?"},
+ color = 33,
},
["Cython"] = {
icon = " ",
files = {},
extensions = {"pyx", "pxd", "pxi"},
modelines = {},
+ color = 27,
},
["R"] = {
icon = " ",
files = {},
extensions = {"r"},
modelines = {},
+ color = 27,
},
["reStructuredText"] = {
icon = "",
files = {},
extensions = {"rst"},
modelines = {},
+ color = 234,
},
["Racket"] = {
icon = " ",
files = {},
extensions = {"rkt"},
modelines = {},
+ color = 160,
},
["Ruby"] = {
icon = " ",
files = {},
extensions = {"rb", "ruby"},
modelines = {"#!\\s*/usr/bin/(env )?ruby"},
+ color = 196,
},
["Rust"] = {
icon = " ",
files = {},
extensions = {"rs"},
modelines = {"#!\\s*/usr/bin/(env )?rust"},
+ color = 166,
},
["Shell"] = {
icon = " ",
@@ -690,131 +777,153 @@ file_types = {
"#!\\s*/bin/(sh|bash)",
"#!\\s*/usr/bin/env bash",
},
+ color = 250,
},
["SCSS"] = {
icon = " ",
files = {},
extensions = {"scss"},
modelines = {},
+ color = 200,
},
["SQL"] = {
icon = " ",
files = {},
extensions = {"sql"},
modelines = {},
+ color = 75,
},
["Sass"] = {
icon = " ",
files = {},
extensions = {"sass"},
modelines = {},
+ color = 200,
},
["Scala"] = {
icon = "",
files = {},
extensions = {"scala"},
modelines = {},
+ color = 196,
},
["Scheme"] = {
icon = "",
files = {},
extensions = {"scm"},
modelines = {},
+ color = 233,
},
["Smalltalk"] = {
icon = " ",
files = {},
extensions = {"st"},
modelines = {},
+ color = 33,
},
["Swift"] = {
icon = " ",
files = {},
extensions = {"swift"},
modelines = {},
+ color = 202,
},
["TOML"] = {
icon = " ",
files = {},
extensions = {"toml"},
modelines = {},
+ color = 209,
},
["Tcl"] = {
icon = " ",
files = {},
extensions = {"tcl"},
modelines = {"#!\\s*/usr/bin/(env )?tcl"},
+ color = 196,
},
["TeX"] = {
icon = " ",
files = {},
extensions = {"tex"},
modelines = {},
+ color = 233,
},
["TypeScript"] = {
icon = " ",
files = {},
extensions = {"ts", "tsx"},
modelines = {},
+ color = 27,
},
["Plain Text"] = {
icon = " ",
files = {},
extensions = {"txt"},
modelines = {},
+ color = 250,
},
["Vala"] = {
icon = " ",
files = {},
extensions = {"vala"},
modelines = {},
+ color = 135,
},
["Visual Basic"] = {
icon = " ",
files = {},
extensions = {"vb", "vbs"},
modelines = {},
+ color = 69,
},
["Vue"] = {
icon = " ",
files = {},
extensions = {"vue"},
modelines = {},
+ color = 40,
},
["Logos"] = {
icon = " ",
files = {},
extensions = {"xm", "x", "xi"},
modelines = {},
+ color = 196,
},
["XML"] = {
icon = " ",
files = {},
extensions = {"xml"},
modelines = {},
+ color = 33,
},
["Yacc"] = {
icon = " ",
files = {},
extensions = {"y", "yacc"},
modelines = {},
+ color = 249,
},
["Yaml"] = {
icon = " ",
files = {},
extensions = {"yaml", "yml"},
modelines = {},
+ color = 161,
},
["Bison"] = {
icon = " ",
files = {},
extensions = {"yxx"},
modelines = {},
+ color = 249,
},
["Zsh"] = {
icon = " ",
files = {},
extensions = {"zsh"},
modelines = {},
+ color = 208,
},
}
From 594e75c4928cb351c88bb7a08363aec56320764f Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Tue, 3 Dec 2024 20:53:01 +0000
Subject: [PATCH 23/51] rustfmt
---
src/editor/filetree.rs | 3 +--
src/editor/filetypes.rs | 1 -
src/editor/interface.rs | 2 +-
src/editor/mod.rs | 2 +-
4 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index 74866a10..28b4e502 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -1,6 +1,5 @@
/// Utilities for handling the file tree
-
-use crate::config::{FileTree as CfgFT, Color};
+use crate::config::{Color, FileTree as CfgFT};
use crate::editor::FileLayout;
use crate::{config, Editor, FileTypes, OxError, Result};
use kaolinite::utils::{file_or_dir, get_cwd, get_file_name};
diff --git a/src/editor/filetypes.rs b/src/editor/filetypes.rs
index abda5450..55a9f7dc 100644
--- a/src/editor/filetypes.rs
+++ b/src/editor/filetypes.rs
@@ -1,5 +1,4 @@
/// Tools for managing and identifying file types
-
use crate::config;
use crate::config::Color;
use crate::editor::Config;
diff --git a/src/editor/interface.rs b/src/editor/interface.rs
index f42e880b..35f10a03 100644
--- a/src/editor/interface.rs
+++ b/src/editor/interface.rs
@@ -1,6 +1,6 @@
use crate::config::SyntaxHighlighting as SH;
/// Functions for rendering the UI
-use crate::editor::{FileLayout, FTParts};
+use crate::editor::{FTParts, FileLayout};
use crate::error::{OxError, Result};
use crate::events::wait_for_event_hog;
use crate::ui::{key_event, size, Feedback};
diff --git a/src/editor/mod.rs b/src/editor/mod.rs
index a85524ed..b0456863 100644
--- a/src/editor/mod.rs
+++ b/src/editor/mod.rs
@@ -28,7 +28,7 @@ mod scanning;
pub use cursor::{allowed_by_multi_cursor, handle_multiple_cursors};
pub use documents::{FileContainer, FileLayout};
-pub use filetree::{FileTree, FTParts};
+pub use filetree::{FTParts, FileTree};
pub use filetypes::{FileType, FileTypes};
pub use interface::RenderCache;
pub use macros::MacroMan;
From ea261d88e9fd5334332d6dcfe5143d182e7f3c4b Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Tue, 3 Dec 2024 21:08:38 +0000
Subject: [PATCH 24/51] adjusted some language colours
---
plugins/todo.lua | 2 +-
src/plugin/bootstrap.lua | 10 +++++-----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/plugins/todo.lua b/plugins/todo.lua
index eaf901a6..0a7206c3 100644
--- a/plugins/todo.lua
+++ b/plugins/todo.lua
@@ -12,7 +12,7 @@ file_types["Todo"] = {
extensions = {"todo"},
files = {".todo.md", ".todo"},
modelines = {},
- color = 83,
+ color = 121,
}
-- Add syntax highlighting to .todo files (done todos are comments)
diff --git a/src/plugin/bootstrap.lua b/src/plugin/bootstrap.lua
index b690ed8e..6981f528 100644
--- a/src/plugin/bootstrap.lua
+++ b/src/plugin/bootstrap.lua
@@ -291,7 +291,7 @@ file_types = {
files = {},
extensions = {"cr"},
modelines = {},
- color = 233,
+ color = 241,
},
["Cuda"] = {
icon = " ",
@@ -662,7 +662,7 @@ file_types = {
files = {},
extensions = {"md", "markdown"},
modelines = {},
- color = 234,
+ color = 243,
},
["Nix"] = {
icon = " ",
@@ -746,7 +746,7 @@ file_types = {
files = {},
extensions = {"rst"},
modelines = {},
- color = 234,
+ color = 243,
},
["Racket"] = {
icon = " ",
@@ -812,7 +812,7 @@ file_types = {
files = {},
extensions = {"scm"},
modelines = {},
- color = 233,
+ color = 243,
},
["Smalltalk"] = {
icon = " ",
@@ -847,7 +847,7 @@ file_types = {
files = {},
extensions = {"tex"},
modelines = {},
- color = 233,
+ color = 243,
},
["TypeScript"] = {
icon = " ",
From a44861ee68f39d5efbce4437b44211f3fcd3ec66 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Tue, 3 Dec 2024 21:16:03 +0000
Subject: [PATCH 25/51] adjusted todo plug-in colour
---
plugins/todo.lua | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/plugins/todo.lua b/plugins/todo.lua
index 0a7206c3..c0ad58f8 100644
--- a/plugins/todo.lua
+++ b/plugins/todo.lua
@@ -12,7 +12,7 @@ file_types["Todo"] = {
extensions = {"todo"},
files = {".todo.md", ".todo"},
modelines = {},
- color = 121,
+ color = 247,
}
-- Add syntax highlighting to .todo files (done todos are comments)
From c323aa2063fd7a83dc7b22104f1d831019f4b8c9 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Wed, 4 Dec 2024 13:42:40 +0000
Subject: [PATCH 26/51] Language icon colours can be configured
---
config/.oxrc | 11 ++
src/config/colors.rs | 102 +++++++++++++++++
src/config/mod.rs | 2 +-
src/editor/filetree.rs | 8 +-
src/editor/filetypes.rs | 5 +-
src/editor/interface.rs | 18 ++-
src/plugin/bootstrap.lua | 229 ++++++++++++++++++++-------------------
7 files changed, 256 insertions(+), 119 deletions(-)
diff --git a/config/.oxrc b/config/.oxrc
index a8add456..4f32e0d4 100644
--- a/config/.oxrc
+++ b/config/.oxrc
@@ -410,6 +410,17 @@ colors.file_tree_fg = {255, 255, 255}
colors.file_tree_selection_fg = {255, 255, 255}
colors.file_tree_selection_bg = {59, 59, 130}
+colors.file_tree_red = {240, 56, 36}
+colors.file_tree_orange = {240, 107, 36}
+colors.file_tree_yellow = {240, 236, 36}
+colors.file_tree_green = {35, 240, 144}
+colors.file_tree_lightblue = {36, 219, 240}
+colors.file_tree_darkblue = {36, 117, 240}
+colors.file_tree_purple = {104, 36, 240}
+colors.file_tree_pink = {206, 36, 240}
+colors.file_tree_brown = {158, 94, 94}
+colors.file_tree_grey = {150, 144, 201}
+
-- Configure Line Numbers --
line_numbers.enabled = true
line_numbers.padding_left = 1
diff --git a/src/config/colors.rs b/src/config/colors.rs
index bc8e6e0d..0159e250 100644
--- a/src/config/colors.rs
+++ b/src/config/colors.rs
@@ -41,6 +41,17 @@ pub struct Colors {
pub file_tree_bg: Color,
pub file_tree_selection_fg: Color,
pub file_tree_selection_bg: Color,
+
+ pub file_tree_red: Color,
+ pub file_tree_orange: Color,
+ pub file_tree_yellow: Color,
+ pub file_tree_green: Color,
+ pub file_tree_lightblue: Color,
+ pub file_tree_darkblue: Color,
+ pub file_tree_purple: Color,
+ pub file_tree_pink: Color,
+ pub file_tree_brown: Color,
+ pub file_tree_grey: Color,
}
impl Default for Colors {
@@ -79,6 +90,17 @@ impl Default for Colors {
file_tree_fg: Color::Rgb(255, 255, 255),
file_tree_selection_bg: Color::Rgb(59, 59, 130),
file_tree_selection_fg: Color::Rgb(255, 255, 255),
+
+ file_tree_red: Color::Rgb(240, 56, 36),
+ file_tree_orange: Color::Rgb(240, 107, 36),
+ file_tree_yellow: Color::Rgb(240, 236, 36),
+ file_tree_green: Color::Rgb(35, 240, 144),
+ file_tree_lightblue: Color::Rgb(36, 219, 240),
+ file_tree_darkblue: Color::Rgb(36, 117, 240),
+ file_tree_purple: Color::Rgb(104, 36, 240),
+ file_tree_pink: Color::Rgb(206, 36, 240),
+ file_tree_brown: Color::Rgb(158, 94, 94),
+ file_tree_grey: Color::Rgb(150, 144, 201),
}
}
}
@@ -223,6 +245,86 @@ impl LuaUserData for Colors {
this.file_tree_selection_fg = Color::from_lua(value);
Ok(())
});
+ fields.add_field_method_set("file_tree_red", |_, this, value| {
+ this.file_tree_red = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_red", |_, this, value| {
+ this.file_tree_red = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_orange", |_, this, value| {
+ this.file_tree_orange = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_orange", |_, this, value| {
+ this.file_tree_orange = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_yellow", |_, this, value| {
+ this.file_tree_yellow = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_yellow", |_, this, value| {
+ this.file_tree_yellow = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_green", |_, this, value| {
+ this.file_tree_green = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_green", |_, this, value| {
+ this.file_tree_green = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_lightblue", |_, this, value| {
+ this.file_tree_lightblue = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_lightblue", |_, this, value| {
+ this.file_tree_lightblue = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_darkblue", |_, this, value| {
+ this.file_tree_darkblue = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_darkblue", |_, this, value| {
+ this.file_tree_darkblue = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_purple", |_, this, value| {
+ this.file_tree_purple = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_purple", |_, this, value| {
+ this.file_tree_purple = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_pink", |_, this, value| {
+ this.file_tree_pink = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_pink", |_, this, value| {
+ this.file_tree_pink = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_brown", |_, this, value| {
+ this.file_tree_brown = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_brown", |_, this, value| {
+ this.file_tree_brown = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_grey", |_, this, value| {
+ this.file_tree_grey = Color::from_lua(value);
+ Ok(())
+ });
+ fields.add_field_method_set("file_tree_grey", |_, this, value| {
+ this.file_tree_grey = Color::from_lua(value);
+ Ok(())
+ });
}
}
diff --git a/src/config/mod.rs b/src/config/mod.rs
index c3d5af82..56a379ea 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -376,7 +376,7 @@ impl FromLua for FileTypes {
.pairs::()
.filter_map(|val| if let Ok((_, v)) = val { Some(v) } else { None })
.collect::>();
- let color = Color::from_lua(info.get("color")?);
+ let color = info.get::("color")?;
result.push(FileType {
name,
icon,
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index 28b4e502..5eec7c42 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -1,5 +1,5 @@
/// Utilities for handling the file tree
-use crate::config::{Color, FileTree as CfgFT};
+use crate::config::FileTree as CfgFT;
use crate::editor::FileLayout;
use crate::{config, Editor, FileTypes, OxError, Result};
use kaolinite::utils::{file_or_dir, get_cwd, get_file_name};
@@ -7,7 +7,7 @@ use std::path::{Path, PathBuf};
/// How parts of a file tree are stored
/// (Padding, Icon, Icon Color, File Name)
-pub type FTParts = Vec<(usize, String, Option, String)>;
+pub type FTParts = Vec<(usize, String, Option, String)>;
/// The backend of a file tree - stores the structure of the files and directories
#[derive(Debug)]
@@ -179,7 +179,7 @@ impl FileTree {
}
/// Get a language-related icon for this node
- fn lang_icon(&self, fts: &FileTypes, config: &CfgFT) -> Option<(String, Color)> {
+ fn lang_icon(&self, fts: &FileTypes, config: &CfgFT) -> Option<(String, String)> {
if config.language_icons {
let path = match self {
Self::File { path } | Self::Dir { path, .. } => path,
@@ -192,7 +192,7 @@ impl FileTree {
}
/// Get the appropriate icon
- fn icon(&self, fts: &FileTypes, config: &CfgFT) -> (String, Option) {
+ fn icon(&self, fts: &FileTypes, config: &CfgFT) -> (String, Option) {
let is_file = match self {
Self::File { .. } => true,
Self::Dir { .. } => false,
diff --git a/src/editor/filetypes.rs b/src/editor/filetypes.rs
index 55a9f7dc..feacf8e9 100644
--- a/src/editor/filetypes.rs
+++ b/src/editor/filetypes.rs
@@ -1,6 +1,5 @@
/// Tools for managing and identifying file types
use crate::config;
-use crate::config::Color;
use crate::editor::Config;
use kaolinite::utils::get_file_name;
use kaolinite::Document;
@@ -66,7 +65,7 @@ pub struct FileType {
/// The modelines that files of this type have
pub modelines: Vec,
/// The colour associated with this file type
- pub color: Color,
+ pub color: String,
}
impl Default for FileType {
@@ -77,7 +76,7 @@ impl Default for FileType {
files: vec![],
extensions: vec![],
modelines: vec![],
- color: Color::Transparent,
+ color: "grey".to_string(),
}
}
}
diff --git a/src/editor/interface.rs b/src/editor/interface.rs
index 35f10a03..df135bbb 100644
--- a/src/editor/interface.rs
+++ b/src/editor/interface.rs
@@ -1,5 +1,6 @@
-use crate::config::SyntaxHighlighting as SH;
/// Functions for rendering the UI
+
+use crate::config::SyntaxHighlighting as SH;
use crate::editor::{FTParts, FileLayout};
use crate::error::{OxError, Result};
use crate::events::wait_for_event_hog;
@@ -561,13 +562,26 @@ impl Editor {
let ft_selection_fg = Fg(config!(self.config, colors)
.file_tree_selection_fg
.to_color()?);
+ let ft_colors = config!(self.config, colors);
// Perform the rendering
let mut total_length = 0;
let line = self.render_cache.file_tree.get(y);
let mut line = if let Some((padding, icon, icon_colour, name)) = line {
total_length = padding * 2 + width(icon, 4) + width(name, 4);
if let (Some(colour), false) = (icon_colour, selected) {
- let colour = Fg(colour.to_color()?);
+ let colour = Fg(match colour.as_str() {
+ "red" => ft_colors.file_tree_red.to_color()?,
+ "orange" => ft_colors.file_tree_orange.to_color()?,
+ "yellow" => ft_colors.file_tree_yellow.to_color()?,
+ "green" => ft_colors.file_tree_green.to_color()?,
+ "lightblue" => ft_colors.file_tree_lightblue.to_color()?,
+ "darkblue" => ft_colors.file_tree_darkblue.to_color()?,
+ "purple" => ft_colors.file_tree_purple.to_color()?,
+ "pink" => ft_colors.file_tree_pink.to_color()?,
+ "brown" => ft_colors.file_tree_brown.to_color()?,
+ "grey" => ft_colors.file_tree_grey.to_color()?,
+ _ => Color::White,
+ });
format!("{}{colour}{icon}{ft_fg}{name}", " ".repeat(*padding))
} else {
format!("{}{icon}{name}", " ".repeat(*padding))
diff --git a/src/plugin/bootstrap.lua b/src/plugin/bootstrap.lua
index 6981f528..7bf00eae 100644
--- a/src/plugin/bootstrap.lua
+++ b/src/plugin/bootstrap.lua
@@ -159,615 +159,626 @@ function shell:kill(pid)
end
-- Add types for built-in file type detection
+-- Colours are in the format of a string of:
+-- red
+-- orange
+-- yellow
+-- green
+-- dark blue
+-- light blue
+-- purple
+-- pink
+-- brown
+-- gray
file_types = {
["ABAP"] = {
icon = " ",
files = {},
extensions = {"abap"},
modelines = {},
- color = 21,
+ color = "darkblue",
},
["Ada"] = {
icon = "",
files = {},
extensions = {"ada"},
modelines = {},
- color = 28,
+ color = "green",
},
["AutoHotkey"] = {
icon = " ",
files = {},
extensions = {"ahk", "ahkl"},
modelines = {},
- color = 157,
+ color = "green",
},
["AppleScript"] = {
icon = "",
files = {},
extensions = {"applescript", "scpt"},
modelines = {},
- color = 252,
+ color = "grey",
},
["Arc"] = {
icon = " ",
files = {},
extensions = {"arc"},
modelines = {},
- color = 125,
+ color = "pink",
},
["ASP"] = {
icon = " ",
files = {},
extensions = {"asp", "asax", "ascx", "ashx", "asmx", "aspx", "axd"},
modelines = {},
- color = 33,
+ color = "lightblue",
},
["ActionScript"] = {
icon = " ",
files = {},
extensions = {"as"},
modelines = {},
- color = 202,
+ color = "orange",
},
["AGS Script"] = {
icon = " ",
files = {},
extensions = {"asc", "ash"},
modelines = {},
- color = 69,
+ color = "purple",
},
["Assembly"] = {
icon = " ",
files = {},
extensions = {"asm", "nasm"},
modelines = {},
- color = 250,
+ color = "grey",
},
["Awk"] = {
icon = " ",
files = {},
extensions = {"awk", "auk", "gawk", "mawk", "nawk"},
modelines = {"#!\\s*/usr/bin/(env )?awk"},
- color = 160,
+ color = "red",
},
["Batch"] = {
icon = " ",
files = {},
extensions = {"bat", "cmd"},
modelines = {},
- color = 250,
+ color = "grey",
},
["Brainfuck"] = {
icon = " ",
files = {},
extensions = {"b", "bf"},
modelines = {},
- color = 226,
+ color = "yellow",
},
["C"] = {
icon = " ",
files = {},
extensions = {"c"},
modelines = {},
- color = 33,
+ color = "lightblue",
},
["CMake"] = {
icon = " ",
files = {},
extensions = {"cmake"},
modelines = {},
- color = 40,
+ color = "green",
},
["Cobol"] = {
icon = " ",
files = {},
extensions = {"cbl", "cobol", "cob"},
modelines = {},
- color = 57,
+ color = "purple",
},
["Java"] = {
icon = " ",
files = {},
extensions = {"class", "java"},
modelines = {},
- color = 202,
+ color = "orange",
},
["Clojure"] = {
icon = " ",
files = {},
extensions = {"clj", "cl2", "cljs", "cljx", "cljc"},
modelines = {},
- color = 37,
+ color = "lightblue",
},
["CoffeeScript"] = {
icon = " ",
files = {},
extensions = {"coffee"},
modelines = {},
- color = 130,
+ color = "brown",
},
["Crystal"] = {
icon = " ",
files = {},
extensions = {"cr"},
modelines = {},
- color = 241,
+ color = "grey",
},
["Cuda"] = {
icon = " ",
files = {},
extensions = {"cu", "cuh"},
modelines = {},
- color = 76,
+ color = "green",
},
["C++"] = {
icon = " ",
files = {},
extensions = {"cpp", "cxx"},
modelines = {},
- color = 26,
+ color = "darkblue",
},
["C#"] = {
icon = " ",
files = {},
extensions = {"cs", "cshtml", "csx"},
modelines = {},
- color = 63,
+ color = "purple",
},
["CSS"] = {
icon = " ",
files = {},
extensions = {"css"},
modelines = {},
- color = 99,
+ color = "purple",
},
["CSV"] = {
icon = " ",
files = {},
extensions = {"csv"},
modelines = {},
- color = 248,
+ color = "grey",
},
["D"] = {
icon = " ",
files = {},
extensions = {"d", "di"},
modelines = {},
- color = 197,
+ color = "red",
},
["Dart"] = {
icon = " ",
files = {},
extensions = {"dart"},
modelines = {},
- color = 33,
+ color = "lightblue",
},
["Diff"] = {
icon = " ",
files = {},
extensions = {"diff", "patch"},
modelines = {},
- color = 28,
+ color = "green",
},
["Dockerfile"] = {
icon = " ",
files = {},
extensions = {"dockerfile"},
modelines = {},
- color = 33,
+ color = "lightblue",
},
["Elixir"] = {
icon = " ",
files = {},
extensions = {"ex", "exs"},
modelines = {},
- color = 56,
+ color = "purple",
},
["Elm"] = {
icon = " ",
files = {},
extensions = {"elm"},
modelines = {},
- color = 26,
+ color = "lightblue",
},
["Emacs Lisp"] = {
icon = " ",
files = {},
extensions = {"el"},
modelines = {},
- color = 63,
+ color = "purple",
},
["ERB"] = {
icon = " ",
files = {},
extensions = {"erb"},
modelines = {},
- color = 196,
+ color = "red",
},
["Erlang"] = {
icon = " ",
files = {},
extensions = {"erl", "es"},
modelines = {},
- color = 196,
+ color = "red",
},
["F#"] = {
icon = " ",
files = {},
extensions = {"fs", "fsi", "fsx"},
modelines = {},
- color = 39,
+ color = "lightblue",
},
["FORTRAN"] = {
icon = " ",
files = {},
extensions = {"f", "f90", "fpp", "for"},
modelines = {},
- color = 129,
+ color = "purple",
},
["Fish"] = {
icon = " ",
files = {},
extensions = {"fish"},
modelines = {"#!\\s*/usr/bin/(env )?fish"},
- color = 203,
+ color = "orange",
},
["Forth"] = {
icon = " ",
files = {},
extensions = {"fth"},
modelines = {},
- color = 196,
+ color = "red",
},
["ANTLR"] = {
icon = " ",
files = {},
extensions = {"g4"},
modelines = {},
- color = 196,
+ color = "red",
},
["GDScript"] = {
icon = " ",
files = {},
extensions = {"gd"},
modelines = {},
- color = 27,
+ color = "darkblue",
},
["GLSL"] = {
icon = " ",
files = {},
extensions = {"glsl", "vert", "shader", "geo", "fshader", "vrx", "vsh", "vshader", "frag"},
modelines = {},
- color = 33,
+ color = "lightblue",
},
["Gnuplot"] = {
icon = " ",
files = {},
extensions = {"gnu", "gp", "plot"},
modelines = {},
- color = 249,
+ color = "grey",
},
["Go"] = {
icon = "",
files = {},
extensions = {"go"},
modelines = {},
- color = 33,
+ color = "lightblue",
},
["Groovy"] = {
icon = " ",
files = {},
extensions = {"groovy", "gvy"},
modelines = {},
- color = 39,
+ color = "lightblue",
},
["HLSL"] = {
icon = " ",
files = {},
extensions = {"hlsl"},
modelines = {},
- color = 21,
+ color = "darkblue",
},
["C Header"] = {
icon = " ",
files = {},
extensions = {"h"},
modelines = {},
- color = 33,
+ color = "lightblue",
},
["Haml"] = {
icon = "",
files = {},
extensions = {"haml"},
modelines = {},
- color = 228,
+ color = "yellow",
},
["Handlebars"] = {
icon = " ",
files = {},
extensions = {"handlebars", "hbs"},
modelines = {},
- color = 94,
+ color = "brown",
},
["Haskell"] = {
icon = " ",
files = {},
extensions = {"hs"},
modelines = {},
- color = 93,
+ color = "purple",
},
["C++ Header"] = {
icon = " ",
files = {},
extensions = {"hpp"},
modelines = {},
- color = 26,
+ color = "darkblue",
},
["HTML"] = {
icon = " ",
files = {},
extensions = {"html", "htm", "xhtml"},
modelines = {},
- color = 208,
+ color = "orange",
},
["INI"] = {
icon = " ",
files = {},
extensions = {"ini", "cfg"},
modelines = {},
- color = 248,
+ color = "grey",
},
["Arduino"] = {
icon = " ",
files = {},
extensions = {"ino"},
modelines = {},
- color = 75,
+ color = "lightblue",
},
["J"] = {
icon = " ",
files = {},
extensions = {"ijs"},
modelines = {},
- color = 45,
+ color = "lightblue",
},
["JSON"] = {
icon = " ",
files = {},
extensions = {"json"},
modelines = {},
- color = 247,
+ color = "grey",
},
["JSX"] = {
icon = " ",
files = {},
extensions = {"jsx"},
modelines = {},
- color = 33,
+ color = "lightblue",
},
["JavaScript"] = {
icon = " ",
files = {},
extensions = {"js"},
modelines = {"#!\\s*/usr/bin/(env )?node"},
- color = 220,
+ color = "yellow",
},
["Julia"] = {
icon = " ",
files = {},
extensions = {"jl"},
modelines = {},
- color = 27,
+ color = "lightblue",
},
["Kotlin"] = {
icon = " ",
files = {},
extensions = {"kt", "ktm", "kts"},
modelines = {},
- color = 129,
+ color = "purple",
},
["LLVM"] = {
icon = " ",
files = {},
extensions = {"ll"},
modelines = {},
- color = 39,
+ color = "lightblue",
},
["Lex"] = {
icon = " ",
files = {},
extensions = {"l", "lex"},
modelines = {},
- color = 249,
+ color = "grey",
},
["Lua"] = {
icon = " ",
files = {".oxrc"},
extensions = {"lua"},
modelines = {"#!\\s*/usr/bin/(env )?lua"},
- color = 27,
+ color = "darkblue",
},
["LiveScript"] = {
icon = " ",
files = {},
extensions = {"ls"},
modelines = {},
- color = 39,
+ color = "lightblue",
},
["LOLCODE"] = {
icon = " ",
files = {},
extensions = {"lol"},
modelines = {},
- color = 160,
+ color = "red",
},
["Common Lisp"] = {
icon = " ",
files = {},
extensions = {"lisp", "asd", "lsp"},
modelines = {},
- color = 243,
+ color = "grey",
},
["Log file"] = {
icon = " ",
files = {},
extensions = {"log"},
modelines = {},
- color = 248,
+ color = "grey",
},
["M4"] = {
icon = " ",
files = {},
extensions = {"m4"},
modelines = {},
- color = 21,
+ color = "darkblue",
},
["Groff"] = {
icon = " ",
files = {},
extensions = {"man", "roff"},
modelines = {},
- color = 249,
+ color = "grey",
},
["Matlab"] = {
icon = " ",
files = {},
extensions = {"matlab"},
modelines = {},
- color = 202,
+ color = "orange",
},
["Objective-C"] = {
icon = " ",
files = {},
extensions = {"m"},
modelines = {},
- color = 27,
+ color = "lightblue",
},
["OCaml"] = {
icon = " ",
files = {},
extensions = {"ml"},
modelines = {},
- color = 208,
+ color = "orange",
},
["Makefile"] = {
icon = " ",
files = {"Makefile"},
extensions = {"mk", "mak"},
modelines = {},
- color = 249,
+ color = "grey",
},
["Markdown"] = {
icon = " ",
files = {},
extensions = {"md", "markdown"},
modelines = {},
- color = 243,
+ color = "grey",
},
["Nix"] = {
icon = " ",
files = {},
extensions = {"nix"},
modelines = {},
- color = 33,
+ color = "lightblue",
},
["NumPy"] = {
icon = " ",
files = {},
extensions = {"numpy"},
modelines = {},
- color = 27,
+ color = "darkblue",
},
["OpenCL"] = {
icon = " ",
files = {},
extensions = {"opencl", "cl"},
modelines = {},
- color = 76,
+ color = "green",
},
["PHP"] = {
icon = " ",
files = {},
extensions = {"php"},
modelines = {"#!\\s*/usr/bin/(env )?php"},
- color = 69,
+ color = "lightblue",
},
["Pascal"] = {
icon = " ",
files = {},
extensions = {"pas"},
modelines = {},
- color = 21,
+ color = "darkblue",
},
["Perl"] = {
icon = " ",
files = {},
extensions = {"pl"},
modelines = {"#!\\s*/usr/bin/(env )?perl"},
- color = 39,
+ color = "lightblue",
},
["PowerShell"] = {
icon = " ",
files = {},
extensions = {"psl"},
modelines = {},
- color = 33,
+ color = "lightblue",
},
["Prolog"] = {
icon = " ",
files = {},
extensions = {"pro"},
modelines = {},
- color = 202,
+ color = "orange",
},
["Python"] = {
icon = " ",
files = {},
extensions = {"py", "pyw"},
modelines = {"#!\\s*/usr/bin/(env )?python3?"},
- color = 33,
+ color = "lightblue",
},
["Cython"] = {
icon = " ",
files = {},
extensions = {"pyx", "pxd", "pxi"},
modelines = {},
- color = 27,
+ color = "darkblue",
},
["R"] = {
icon = " ",
files = {},
extensions = {"r"},
modelines = {},
- color = 27,
+ color = "darkblue",
},
["reStructuredText"] = {
icon = "",
files = {},
extensions = {"rst"},
modelines = {},
- color = 243,
+ color = "grey",
},
["Racket"] = {
icon = " ",
files = {},
extensions = {"rkt"},
modelines = {},
- color = 160,
+ color = "red",
},
["Ruby"] = {
icon = " ",
files = {},
extensions = {"rb", "ruby"},
modelines = {"#!\\s*/usr/bin/(env )?ruby"},
- color = 196,
+ color = "red",
},
["Rust"] = {
icon = " ",
files = {},
extensions = {"rs"},
modelines = {"#!\\s*/usr/bin/(env )?rust"},
- color = 166,
+ color = "orange",
},
["Shell"] = {
icon = " ",
@@ -777,153 +788,153 @@ file_types = {
"#!\\s*/bin/(sh|bash)",
"#!\\s*/usr/bin/env bash",
},
- color = 250,
+ color = "grey",
},
["SCSS"] = {
icon = " ",
files = {},
extensions = {"scss"},
modelines = {},
- color = 200,
+ color = "pink",
},
["SQL"] = {
icon = " ",
files = {},
extensions = {"sql"},
modelines = {},
- color = 75,
+ color = "lightblue",
},
["Sass"] = {
icon = " ",
files = {},
extensions = {"sass"},
modelines = {},
- color = 200,
+ color = "pink",
},
["Scala"] = {
icon = "",
files = {},
extensions = {"scala"},
modelines = {},
- color = 196,
+ color = "red",
},
["Scheme"] = {
icon = "",
files = {},
extensions = {"scm"},
modelines = {},
- color = 243,
+ color = "grey",
},
["Smalltalk"] = {
icon = " ",
files = {},
extensions = {"st"},
modelines = {},
- color = 33,
+ color = "lightblue",
},
["Swift"] = {
icon = " ",
files = {},
extensions = {"swift"},
modelines = {},
- color = 202,
+ color = "orange",
},
["TOML"] = {
icon = " ",
files = {},
extensions = {"toml"},
modelines = {},
- color = 209,
+ color = "orange",
},
["Tcl"] = {
icon = " ",
files = {},
extensions = {"tcl"},
modelines = {"#!\\s*/usr/bin/(env )?tcl"},
- color = 196,
+ color = "red",
},
["TeX"] = {
icon = " ",
files = {},
extensions = {"tex"},
modelines = {},
- color = 243,
+ color = "grey",
},
["TypeScript"] = {
icon = " ",
files = {},
extensions = {"ts", "tsx"},
modelines = {},
- color = 27,
+ color = "darkblue",
},
["Plain Text"] = {
icon = " ",
files = {},
extensions = {"txt"},
modelines = {},
- color = 250,
+ color = "grey",
},
["Vala"] = {
icon = " ",
files = {},
extensions = {"vala"},
modelines = {},
- color = 135,
+ color = "purple",
},
["Visual Basic"] = {
icon = " ",
files = {},
extensions = {"vb", "vbs"},
modelines = {},
- color = 69,
+ color = "purple",
},
["Vue"] = {
icon = " ",
files = {},
extensions = {"vue"},
modelines = {},
- color = 40,
+ color = "green",
},
["Logos"] = {
icon = " ",
files = {},
extensions = {"xm", "x", "xi"},
modelines = {},
- color = 196,
+ color = "red",
},
["XML"] = {
icon = " ",
files = {},
extensions = {"xml"},
modelines = {},
- color = 33,
+ color = "lightblue",
},
["Yacc"] = {
icon = " ",
files = {},
extensions = {"y", "yacc"},
modelines = {},
- color = 249,
+ color = "grey",
},
["Yaml"] = {
icon = " ",
files = {},
extensions = {"yaml", "yml"},
modelines = {},
- color = 161,
+ color = "red",
},
["Bison"] = {
icon = " ",
files = {},
extensions = {"yxx"},
modelines = {},
- color = 249,
+ color = "grey",
},
["Zsh"] = {
icon = " ",
files = {},
extensions = {"zsh"},
modelines = {},
- color = 208,
+ color = "orange",
},
}
From 2899535274b8c05d147de020f26d60e2cce5291a Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Wed, 4 Dec 2024 14:03:58 +0000
Subject: [PATCH 27/51] Theme specific icon colours
---
config/.oxrc | 16 ++++++++--------
plugins/themes/default16.lua | 11 +++++++++++
plugins/themes/galaxy.lua | 11 +++++++++++
plugins/themes/omni.lua | 11 +++++++++++
plugins/themes/tropical.lua | 11 +++++++++++
plugins/todo.lua | 2 +-
6 files changed, 53 insertions(+), 9 deletions(-)
diff --git a/config/.oxrc b/config/.oxrc
index 4f32e0d4..4bab180a 100644
--- a/config/.oxrc
+++ b/config/.oxrc
@@ -410,14 +410,14 @@ colors.file_tree_fg = {255, 255, 255}
colors.file_tree_selection_fg = {255, 255, 255}
colors.file_tree_selection_bg = {59, 59, 130}
-colors.file_tree_red = {240, 56, 36}
-colors.file_tree_orange = {240, 107, 36}
-colors.file_tree_yellow = {240, 236, 36}
-colors.file_tree_green = {35, 240, 144}
-colors.file_tree_lightblue = {36, 219, 240}
-colors.file_tree_darkblue = {36, 117, 240}
-colors.file_tree_purple = {104, 36, 240}
-colors.file_tree_pink = {206, 36, 240}
+colors.file_tree_red = {240, 104, 89}
+colors.file_tree_orange = {240, 142, 89}
+colors.file_tree_yellow = {240, 237, 89}
+colors.file_tree_green = {89, 240, 169}
+colors.file_tree_lightblue = {89, 225, 240}
+colors.file_tree_darkblue = {89, 149, 240}
+colors.file_tree_purple = {139, 89, 240}
+colors.file_tree_pink = {215, 89, 240}
colors.file_tree_brown = {158, 94, 94}
colors.file_tree_grey = {150, 144, 201}
diff --git a/plugins/themes/default16.lua b/plugins/themes/default16.lua
index 8b686f2b..12771158 100644
--- a/plugins/themes/default16.lua
+++ b/plugins/themes/default16.lua
@@ -53,6 +53,17 @@ colors.file_tree_fg = white
colors.file_tree_selection_bg = darkgrey
colors.file_tree_selection_fg = cyan
+colors.file_tree_red = red
+colors.file_tree_orange = darkyellow
+colors.file_tree_yellow = yellow
+colors.file_tree_green = green
+colors.file_tree_lightblue = blue
+colors.file_tree_darkblue = darkblue
+colors.file_tree_purple = darkmagenta
+colors.file_tree_pink = magenta
+colors.file_tree_brown = darkred
+colors.file_tree_grey = grey
+
-- Configure Syntax Highlighting Colours --
syntax:set("string", green) -- Strings, bright green
syntax:set("comment", darkgrey) -- Comments, light purple/gray
diff --git a/plugins/themes/galaxy.lua b/plugins/themes/galaxy.lua
index fc8035d3..e1da9e5e 100644
--- a/plugins/themes/galaxy.lua
+++ b/plugins/themes/galaxy.lua
@@ -49,6 +49,17 @@ colors.file_tree_fg = white
colors.file_tree_selection_bg = purple
colors.file_tree_selection_fg = black
+colors.file_tree_red = {247, 156, 156}
+colors.file_tree_orange = {247, 165, 156}
+colors.file_tree_yellow = {247, 226, 156}
+colors.file_tree_green = {191, 247, 156}
+colors.file_tree_lightblue = {156, 214, 247}
+colors.file_tree_darkblue = {156, 163, 247}
+colors.file_tree_purple = {197, 156, 247}
+colors.file_tree_pink = {246, 156, 247}
+colors.file_tree_brown = {163, 118, 118}
+colors.file_tree_grey = {160, 157, 191}
+
-- Configure Syntax Highlighting Colours --
syntax:set("string", green) -- Strings, bright green
syntax:set("comment", grey3) -- Comments, light purple/gray
diff --git a/plugins/themes/omni.lua b/plugins/themes/omni.lua
index 6f4244e4..7a1f6130 100644
--- a/plugins/themes/omni.lua
+++ b/plugins/themes/omni.lua
@@ -49,6 +49,17 @@ colors.file_tree_fg = foreground
colors.file_tree_selection_bg = pink
colors.file_tree_selection_fg = background
+colors.file_tree_red = {255, 128, 128}
+colors.file_tree_orange = {255, 155, 128}
+colors.file_tree_yellow = {255, 204, 128}
+colors.file_tree_green = {196, 255, 128}
+colors.file_tree_lightblue = {128, 236, 255}
+colors.file_tree_darkblue = {128, 147, 255}
+colors.file_tree_purple = {204, 128, 255}
+colors.file_tree_pink = {255, 128, 200}
+colors.file_tree_brown = {163, 108, 108}
+colors.file_tree_grey = {155, 153, 176}
+
-- Configure Syntax Highlighting Colours --
syntax:set("string", yellow) -- Strings, fresh green
syntax:set("comment", comment) -- Comments, muted and subtle
diff --git a/plugins/themes/tropical.lua b/plugins/themes/tropical.lua
index 38c48cb9..a4033fa2 100644
--- a/plugins/themes/tropical.lua
+++ b/plugins/themes/tropical.lua
@@ -49,6 +49,17 @@ colors.file_tree_fg = white
colors.file_tree_selection_bg = lightblue
colors.file_tree_selection_fg = black
+colors.file_tree_red = {245, 127, 127}
+colors.file_tree_orange = {245, 169, 127}
+colors.file_tree_yellow = {245, 217, 127}
+colors.file_tree_green = {165, 245, 127}
+colors.file_tree_lightblue = {127, 227, 245}
+colors.file_tree_darkblue = {127, 145, 245}
+colors.file_tree_purple = {190, 127, 245}
+colors.file_tree_pink = {245, 127, 217}
+colors.file_tree_brown = {163, 116, 116}
+colors.file_tree_grey = {191, 190, 196}
+
-- Configure Syntax Highlighting Colours --
syntax:set("string", lightblue) -- Strings, bright green
syntax:set("comment", grey3) -- Comments, light purple/gray
diff --git a/plugins/todo.lua b/plugins/todo.lua
index c0ad58f8..f95c9661 100644
--- a/plugins/todo.lua
+++ b/plugins/todo.lua
@@ -12,7 +12,7 @@ file_types["Todo"] = {
extensions = {"todo"},
files = {".todo.md", ".todo"},
modelines = {},
- color = 247,
+ color = "grey",
}
-- Add syntax highlighting to .todo files (done todos are comments)
From b884fdf110a66a906d1dca837a947533ab9ee2f6 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Wed, 4 Dec 2024 14:17:37 +0000
Subject: [PATCH 28/51] Better file tree width management
---
config/.oxrc | 2 +-
src/config/filetree.rs | 4 ++--
src/editor/filetree.rs | 6 ++++--
src/editor/interface.rs | 8 +++++++-
4 files changed, 14 insertions(+), 6 deletions(-)
diff --git a/config/.oxrc b/config/.oxrc
index 4bab180a..ae81a5a7 100644
--- a/config/.oxrc
+++ b/config/.oxrc
@@ -431,7 +431,7 @@ terminal.mouse_enabled = true
terminal.scroll_amount = 4
-- Configure File Tree --
-file_tree.width = 0.2
+file_tree.width = 30
file_tree.move_focus_to_file = true
file_tree.icons = false
file_tree.language_icons = true
diff --git a/src/config/filetree.rs b/src/config/filetree.rs
index 6401d2f9..44f0e75e 100644
--- a/src/config/filetree.rs
+++ b/src/config/filetree.rs
@@ -3,7 +3,7 @@ use mlua::prelude::*;
#[derive(Debug)]
pub struct FileTree {
- pub width: f64,
+ pub width: usize,
pub move_focus_to_file: bool,
pub icons: bool,
pub language_icons: bool,
@@ -12,7 +12,7 @@ pub struct FileTree {
impl Default for FileTree {
fn default() -> Self {
Self {
- width: 0.2,
+ width: 30,
move_focus_to_file: true,
icons: false,
language_icons: true,
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index 5eec7c42..2f2e0087 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -1,6 +1,7 @@
/// Utilities for handling the file tree
use crate::config::FileTree as CfgFT;
use crate::editor::FileLayout;
+use crate::ui::size;
use crate::{config, Editor, FileTypes, OxError, Result};
use kaolinite::utils::{file_or_dir, get_cwd, get_file_name};
use std::path::{Path, PathBuf};
@@ -298,8 +299,9 @@ impl Editor {
pub fn open_file_tree(&mut self) {
if !self.file_tree_is_open() {
// Calculate display proportions
- let width = config!(self.config, file_tree).width;
- let other = if width <= 1.0 { 1.0 - width } else { 0.0 };
+ let total_width = size().map(|s| s.w as f64).unwrap_or(1.0);
+ let width = config!(self.config, file_tree).width as f64 / total_width;
+ let other = 1.0 - width as f64;
// Set up file tree values
self.old_ptr = self.ptr.clone();
if let Some(cwd) = get_cwd() {
diff --git a/src/editor/interface.rs b/src/editor/interface.rs
index df135bbb..017afb28 100644
--- a/src/editor/interface.rs
+++ b/src/editor/interface.rs
@@ -1,5 +1,4 @@
/// Functions for rendering the UI
-
use crate::config::SyntaxHighlighting as SH;
use crate::editor::{FTParts, FileLayout};
use crate::error::{OxError, Result};
@@ -589,6 +588,13 @@ impl Editor {
} else {
String::new()
};
+ while total_length > length {
+ if let Some(ch) = line.pop() {
+ total_length -= width_char(&ch, 4);
+ } else {
+ break;
+ }
+ }
line += &" ".repeat(length.saturating_sub(total_length));
// Return result
if selected {
From fa797c89e1ea384eab795a0a52053892bc16b372 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Wed, 4 Dec 2024 15:19:32 +0000
Subject: [PATCH 29/51] Option to back up to parent directory
---
src/editor/filetree.rs | 67 +++++++++++++++++++++++++++++++++++++-----
1 file changed, 60 insertions(+), 7 deletions(-)
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index 2f2e0087..ee1f98bb 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -11,7 +11,7 @@ use std::path::{Path, PathBuf};
pub type FTParts = Vec<(usize, String, Option, String)>;
/// The backend of a file tree - stores the structure of the files and directories
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub enum FileTree {
/// Represents a file
File { path: String },
@@ -226,6 +226,25 @@ impl FileTree {
/// Display this file tree
pub fn display(&self, sel: &str, fts: &FileTypes, cfg: &CfgFT) -> (FTParts, Option) {
+ let mut result = self.display_recursive(sel, fts, cfg);
+ result
+ .0
+ .insert(0, (0, " ".to_string(), None, "..".to_string()));
+ if sel == ".." {
+ result.1 = Some(0);
+ } else if let Some(ref mut at) = result.1 {
+ *at += 1;
+ }
+ result
+ }
+
+ /// Display this file tree (recursive)
+ pub fn display_recursive(
+ &self,
+ sel: &str,
+ fts: &FileTypes,
+ cfg: &CfgFT,
+ ) -> (FTParts, Option) {
let icons = cfg.icons;
match self {
Self::File { path } => {
@@ -257,7 +276,7 @@ impl FileTree {
// Write child nodes
if let Some(files) = files {
for file in files {
- let (sub_display, sub_at) = file.display(sel, fts, cfg);
+ let (sub_display, sub_at) = file.display_recursive(sel, fts, cfg);
for (c, s) in sub_display.iter().enumerate() {
let mut s = s.clone();
s.0 += 1;
@@ -277,6 +296,13 @@ impl FileTree {
/// Find the file path at a certain index
pub fn flatten(&self) -> Vec {
+ let mut result = self.flatten_recursive();
+ result.insert(0, "..".to_string());
+ result
+ }
+
+ /// Find the file path at a certain index (recursive)
+ pub fn flatten_recursive(&self) -> Vec {
match self {
Self::File { path } => vec![path.to_string()],
Self::Dir { path, files } => {
@@ -284,13 +310,27 @@ impl FileTree {
result.push(path.to_string());
if let Some(files) = files {
for file in files {
- result.append(&mut file.flatten());
+ result.append(&mut file.flatten_recursive());
}
}
result
}
}
}
+
+ /// Expand this file tree upwards towards parent
+ pub fn open_parent(&self) -> Result {
+ if let Self::Dir { path, files } = self {
+ let parent_path = format!("{path}/..");
+ let mut parent = Self::build(&parent_path)?;
+ if let Some(Self::Dir { files: child, .. }) = parent.get_mut(path) {
+ child.clone_from(&files.clone());
+ }
+ Ok(parent)
+ } else {
+ Err(OxError::InvalidPath)
+ }
+ }
}
impl Editor {
@@ -423,10 +463,14 @@ impl Editor {
/// Open a certain file / directory in a file tree
pub fn file_tree_open_node(&mut self) -> Result<()> {
if let Some(file_name) = &self.file_tree_selection.clone() {
- match file_or_dir(file_name) {
- "file" => self.file_tree_open_file()?,
- "directory" => self.file_tree_toggle_dir(),
- _ => (),
+ if file_name == ".." {
+ self.file_tree_open_parent()?;
+ } else {
+ match file_or_dir(file_name) {
+ "file" => self.file_tree_open_file()?,
+ "directory" => self.file_tree_toggle_dir(),
+ _ => (),
+ }
}
}
Ok(())
@@ -454,6 +498,7 @@ impl Editor {
Ok(())
}
+ /// Toggle a directory to expand or contract
pub fn file_tree_toggle_dir(&mut self) {
if let Some(ref mut file_tree) = &mut self.file_tree {
if let Some(file_name) = self.file_tree_selection.as_ref() {
@@ -471,4 +516,12 @@ impl Editor {
}
}
}
+
+ /// Expand this tree up to the parent
+ pub fn file_tree_open_parent(&mut self) -> Result<()> {
+ if let Some(ref mut file_tree) = &mut self.file_tree {
+ self.file_tree = Some(file_tree.open_parent()?);
+ }
+ Ok(())
+ }
}
From a7883adc60c92f2f410dfc34c925d3d64a128d21 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Wed, 4 Dec 2024 15:30:53 +0000
Subject: [PATCH 30/51] Added convenient file tree shortcuts
---
src/editor/filetree.rs | 37 +++++++++++++++++++++++++++++++++++++
src/editor/mod.rs | 3 +++
2 files changed, 40 insertions(+)
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index ee1f98bb..3fb4b132 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -524,4 +524,41 @@ impl Editor {
}
Ok(())
}
+
+ /// Expand this tree up to the parent
+ pub fn file_tree_move_into(&mut self) {
+ if let Some(ref mut file_tree) = &mut self.file_tree {
+ if let Some(file_name) = self.file_tree_selection.as_ref() {
+ if let Some(node) = file_tree.get_mut(file_name) {
+ if let FileTree::Dir { files, .. } = node {
+ if files.is_none() {
+ // Expand if not already expanded
+ node.expand();
+ }
+ *file_tree = node.clone();
+ }
+ }
+ }
+ }
+ }
+
+ /// Move to the top of the file tree
+ pub fn file_tree_move_to_top(&mut self) {
+ if let Some(ref mut file_tree) = &mut self.file_tree {
+ self.file_tree_selection = file_tree
+ .flatten()
+ .first()
+ .map(std::string::ToString::to_string);
+ }
+ }
+
+ /// Move to the bottom of the file tree
+ pub fn file_tree_move_to_bottom(&mut self) {
+ if let Some(ref mut file_tree) = &mut self.file_tree {
+ self.file_tree_selection = file_tree
+ .flatten()
+ .last()
+ .map(std::string::ToString::to_string);
+ }
+ }
}
diff --git a/src/editor/mod.rs b/src/editor/mod.rs
index b0456863..5c1cad25 100644
--- a/src/editor/mod.rs
+++ b/src/editor/mod.rs
@@ -456,6 +456,9 @@ impl Editor {
(KMod::NONE, KCode::Up) => self.file_tree_select_up(),
(KMod::NONE, KCode::Down) => self.file_tree_select_down(),
(KMod::NONE, KCode::Enter) => self.file_tree_open_node()?,
+ (KMod::CONTROL, KCode::Up) => self.file_tree_move_to_top(),
+ (KMod::CONTROL, KCode::Down) => self.file_tree_move_to_bottom(),
+ (KMod::CONTROL, KCode::Enter) => self.file_tree_move_into(),
_ => (),
}
} else {
From 0a5dca22f06edbe2389cdffac8d75971c8995b7e Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Wed, 4 Dec 2024 17:08:00 +0000
Subject: [PATCH 31/51] Added new/delete/move/copy options to file tree
---
src/editor/filetree.rs | 114 ++++++++++++++++++++++++++++++++++++++++-
src/editor/mod.rs | 4 ++
2 files changed, 117 insertions(+), 1 deletion(-)
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index 3fb4b132..5e1bf87c 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -2,7 +2,7 @@
use crate::config::FileTree as CfgFT;
use crate::editor::FileLayout;
use crate::ui::size;
-use crate::{config, Editor, FileTypes, OxError, Result};
+use crate::{config, Editor, Feedback, FileTypes, OxError, Result};
use kaolinite::utils::{file_or_dir, get_cwd, get_file_name};
use std::path::{Path, PathBuf};
@@ -331,6 +331,45 @@ impl FileTree {
Err(OxError::InvalidPath)
}
}
+
+ /// Get all directories that have been expanded
+ pub fn get_expanded(&mut self) -> Vec {
+ let mut result = vec![];
+ match self {
+ Self::File { .. } => (),
+ Self::Dir { files, path } => {
+ if let Some(files) = files {
+ // Pre-order traversal - very important this remains!
+ result.push(path.clone());
+ for file in files {
+ result.append(&mut file.get_expanded());
+ }
+ }
+ }
+ }
+ result
+ }
+
+ /// Refresh all open directories
+ pub fn refresh(&mut self) {
+ if let Self::Dir {
+ files: Some(_),
+ path,
+ } = self
+ {
+ // Rebuild the tree
+ if let Ok(mut result) = Self::build(path) {
+ // Re expand the tree
+ let expanded = self.get_expanded();
+ for dir in expanded {
+ if let Some(dir) = result.get_mut(&dir) {
+ dir.expand();
+ }
+ }
+ *self = result;
+ }
+ }
+ }
}
impl Editor {
@@ -561,4 +600,77 @@ impl Editor {
.map(std::string::ToString::to_string);
}
}
+
+ /// Create a new file / folder
+ pub fn file_tree_new(&mut self) -> Result<()> {
+ let path = self.path_prompt()?;
+ if path.ends_with(std::path::MAIN_SEPARATOR) {
+ std::fs::create_dir_all(path)?;
+ self.file_tree_refresh();
+ self.feedback = Feedback::Info("Folder created".to_string());
+ } else {
+ std::fs::File::create(path)?;
+ self.file_tree_refresh();
+ self.feedback = Feedback::Info("File created".to_string());
+ }
+ Ok(())
+ }
+
+ /// Delete a file
+ pub fn file_tree_delete(&mut self) -> Result<()> {
+ if let Some(file_name) = &self.file_tree_selection.clone() {
+ let prompt =
+ self.prompt(format!("Are you sure you wish to delete {file_name} (y/n)"))?;
+ if prompt == "y" {
+ if file_or_dir(file_name) == "file" {
+ std::fs::remove_file(file_name)?;
+ self.file_tree_refresh();
+ self.file_tree_select_up();
+ self.feedback = Feedback::Info("File deleted".to_string());
+ } else {
+ self.feedback = Feedback::Error(
+ "Folders can't be deleted in Ox: too dangerous".to_string(),
+ );
+ }
+ }
+ }
+ Ok(())
+ }
+
+ /// Copy a file
+ pub fn file_tree_copy(&mut self) -> Result<()> {
+ if let Some(old_file) = &self.file_tree_selection.clone() {
+ let path = self.path_prompt()?;
+ if file_or_dir(old_file) == "file" {
+ std::fs::copy(old_file, path)?;
+ self.file_tree_refresh();
+ self.feedback = Feedback::Info("File copied".to_string());
+ } else {
+ self.feedback = Feedback::Error("Not a file".to_string());
+ }
+ }
+ Ok(())
+ }
+
+ /// Move (or rename) a file / folder
+ pub fn file_tree_move(&mut self) -> Result<()> {
+ if let Some(old_file) = &self.file_tree_selection.clone() {
+ let path = self.path_prompt()?;
+ std::fs::rename(old_file, path.clone())?;
+ self.file_tree_refresh();
+ if file_or_dir(&path) == "file" {
+ self.feedback = Feedback::Info("File moved".to_string());
+ } else if file_or_dir(&path) == "directory" {
+ self.feedback = Feedback::Info("Folder moved".to_string());
+ }
+ }
+ Ok(())
+ }
+
+ /// Refresh the file tree
+ pub fn file_tree_refresh(&mut self) {
+ if let Some(ref mut file_tree) = self.file_tree {
+ file_tree.refresh();
+ }
+ }
}
diff --git a/src/editor/mod.rs b/src/editor/mod.rs
index 5c1cad25..61cd5490 100644
--- a/src/editor/mod.rs
+++ b/src/editor/mod.rs
@@ -459,6 +459,10 @@ impl Editor {
(KMod::CONTROL, KCode::Up) => self.file_tree_move_to_top(),
(KMod::CONTROL, KCode::Down) => self.file_tree_move_to_bottom(),
(KMod::CONTROL, KCode::Enter) => self.file_tree_move_into(),
+ (KMod::NONE, KCode::Char('n')) => self.file_tree_new()?,
+ (KMod::NONE, KCode::Char('d')) => self.file_tree_delete()?,
+ (KMod::NONE, KCode::Char('m')) => self.file_tree_move()?,
+ (KMod::NONE, KCode::Char('c')) => self.file_tree_copy()?,
_ => (),
}
} else {
From d2a0e75bb635669bccddecf38b68e8da3e751fe6 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Thu, 5 Dec 2024 01:41:03 +0000
Subject: [PATCH 32/51] Update README.md
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index f840f713..3759db01 100644
--- a/README.md
+++ b/README.md
@@ -62,6 +62,7 @@ It works best on linux, but macOS and Windows are also supported.
- :writing_hand: Convenient shortcuts when writing code
- :crossed_swords: Multi-editing features such as multiple cursors and recordable macros
- :window: Splits to view multiple documents on the same screen at the same time
+- :file_folder: File tree to view, open, create, delete, copy and move files
### Robustness
From 10a2ac7a663b977b8a3bf98aa4bf7dfb7632fa1d Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Thu, 5 Dec 2024 01:43:08 +0000
Subject: [PATCH 33/51] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 3759db01..9ae5aa73 100644
--- a/README.md
+++ b/README.md
@@ -62,7 +62,7 @@ It works best on linux, but macOS and Windows are also supported.
- :writing_hand: Convenient shortcuts when writing code
- :crossed_swords: Multi-editing features such as multiple cursors and recordable macros
- :window: Splits to view multiple documents on the same screen at the same time
-- :file_folder: File tree to view, open, create, delete, copy and move files
+- :card_box: File tree to view, open, create, delete, copy and move files
### Robustness
From 4721103a04a05cb9498e53f21813d1270d7adbe8 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Thu, 5 Dec 2024 01:43:39 +0000
Subject: [PATCH 34/51] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 9ae5aa73..0c980cd7 100644
--- a/README.md
+++ b/README.md
@@ -62,7 +62,7 @@ It works best on linux, but macOS and Windows are also supported.
- :writing_hand: Convenient shortcuts when writing code
- :crossed_swords: Multi-editing features such as multiple cursors and recordable macros
- :window: Splits to view multiple documents on the same screen at the same time
-- :card_box: File tree to view, open, create, delete, copy and move files
+- :file_cabinet: File tree to view, open, create, delete, copy and move files
### Robustness
From f2403cd6146d4bafb434f5dd1ddee2385e467291 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Thu, 5 Dec 2024 15:54:52 +0000
Subject: [PATCH 35/51] Rejection of opening directories
---
src/editor/mod.rs | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/editor/mod.rs b/src/editor/mod.rs
index 61cd5490..298d848d 100644
--- a/src/editor/mod.rs
+++ b/src/editor/mod.rs
@@ -7,7 +7,7 @@ use crossterm::event::{
Event as CEvent, KeyCode as KCode, KeyModifiers as KMod, MouseEvent, MouseEventKind,
};
use kaolinite::event::Error as KError;
-use kaolinite::utils::{get_absolute_path, get_file_name};
+use kaolinite::utils::{file_or_dir, get_absolute_path, get_file_name};
use kaolinite::{Document, Loc};
use mlua::{Error as LuaError, Lua};
use std::env;
@@ -179,6 +179,13 @@ impl Editor {
/// Function to create a file container
pub fn open_fc(&mut self, file_name: &str) -> Result {
+ // Reject the opening of directories
+ if file_or_dir(file_name) != "file" {
+ return Err(OxError::Kaolinite(KError::Io(std::io::Error::new(
+ std::io::ErrorKind::IsADirectory,
+ "This is a directory, not a file",
+ ))));
+ }
// Check if a file is already opened
if let Some((idx, ptr)) =
self.already_open(&get_absolute_path(file_name).unwrap_or_default())
From 053db8086bf818a444c2b20c0b9b212795c0e9b6 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Thu, 5 Dec 2024 16:15:21 +0000
Subject: [PATCH 36/51] Proper closing in relation to file tree and cleaner
file layout structure
---
src/editor/documents.rs | 39 +++++++++++++++++++++++++++++++++++++++
src/editor/mod.rs | 4 +++-
2 files changed, 42 insertions(+), 1 deletion(-)
diff --git a/src/editor/documents.rs b/src/editor/documents.rs
index ee74a33e..ba81d9fe 100644
--- a/src/editor/documents.rs
+++ b/src/editor/documents.rs
@@ -324,6 +324,24 @@ impl FileLayout {
}
}
+ /// Remove any empty atoms
+ pub fn clean_up_multis(&mut self, mut idx: Vec) -> Vec {
+ // Continue checking for redundant sidebyside / toptobottom
+ while let Some(redundant_idx) = self.redundant_multis(vec![]) {
+ let multi = self.get_raw_mut(redundant_idx.clone());
+ if let Some(layout) = multi {
+ if let Self::SideBySide(layouts) | Self::TopToBottom(layouts) = layout {
+ *layout = layouts[0].0.clone();
+ if idx.starts_with(&redundant_idx) {
+ idx.remove(redundant_idx.len());
+ return idx;
+ }
+ }
+ }
+ }
+ idx
+ }
+
/// Remove a certain index from this tree
#[allow(clippy::cast_precision_loss)]
pub fn remove(&mut self, at: Vec) {
@@ -382,6 +400,27 @@ impl FileLayout {
}
}
+ /// Traverse the tree and return a list of indices to redundant sidebyside/toptobottom
+ pub fn redundant_multis(&self, at: Vec) -> Option> {
+ match self {
+ Self::None | Self::FileTree | Self::Atom(_, _) => None,
+ Self::SideBySide(layouts) | Self::TopToBottom(layouts) => {
+ if layouts.len() == 1 {
+ Some(at)
+ } else {
+ for (c, layout) in layouts.iter().enumerate() {
+ let mut idx = at.clone();
+ idx.push(c);
+ if let Some(result) = layout.0.redundant_multis(idx) {
+ return Some(result);
+ }
+ }
+ None
+ }
+ }
+ }
+ }
+
/// Find a new pointer position when something is removed
pub fn new_pointer_position(&self, old: &[usize]) -> Vec {
// Zoom out until a sidebyside or toptobottom is found
diff --git a/src/editor/mod.rs b/src/editor/mod.rs
index 298d848d..cb520b95 100644
--- a/src/editor/mod.rs
+++ b/src/editor/mod.rs
@@ -348,10 +348,12 @@ impl Editor {
self.files.clean_up();
// Find a new pointer position
self.ptr = self.files.new_pointer_position(&self.ptr);
+ // Clean up the redundant sidebyside/toptobottom
+ self.ptr = self.files.clean_up_multis(self.ptr.clone());
}
}
// If there are no longer any active atoms, quit the entire editor
- self.active = !matches!(self.files, FileLayout::None);
+ self.active = !matches!(self.files, FileLayout::None | FileLayout::FileTree);
Ok(())
}
From 31dedc4da472afeeefaa23087c09f945a1d39c77 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Thu, 5 Dec 2024 16:58:46 +0000
Subject: [PATCH 37/51] Stopped panics using mouse with the file tree
---
src/editor/filetree.rs | 22 ++++------------------
src/editor/mouse.rs | 16 ++++++++++++++++
2 files changed, 20 insertions(+), 18 deletions(-)
diff --git a/src/editor/filetree.rs b/src/editor/filetree.rs
index 5e1bf87c..b73dd23a 100644
--- a/src/editor/filetree.rs
+++ b/src/editor/filetree.rs
@@ -390,24 +390,10 @@ impl Editor {
}
}
// Wrap existing file layout in new file layout
- if let FileLayout::SideBySide(ref mut layouts) = &mut self.files {
- // Shrink existing splits
- let redistribute = width / layouts.len() as f64;
- for (_, prop) in &mut *layouts {
- if *prop >= redistribute {
- *prop -= redistribute;
- } else {
- *prop = 0.0;
- }
- }
- // Insert file tree
- layouts.insert(0, (FileLayout::FileTree, width));
- } else {
- self.files = FileLayout::SideBySide(vec![
- (FileLayout::FileTree, width),
- (self.files.clone(), other),
- ]);
- }
+ self.files = FileLayout::SideBySide(vec![
+ (FileLayout::FileTree, width),
+ (self.files.clone(), other),
+ ]);
self.ptr = vec![0];
}
}
diff --git a/src/editor/mouse.rs b/src/editor/mouse.rs
index e1800910..c31c7f44 100644
--- a/src/editor/mouse.rs
+++ b/src/editor/mouse.rs
@@ -107,6 +107,7 @@ impl Editor {
}
match self.find_mouse_location(lua, event) {
MouseLocation::File(idx, mut loc) => {
+ self.cache_old_ptr(&idx);
self.ptr.clone_from(&idx);
if let Some(doc) = self.try_doc_mut() {
doc.clear_cursors();
@@ -117,6 +118,7 @@ impl Editor {
}
MouseLocation::Tabs(idx, i) => {
self.files.move_to(idx.clone(), i);
+ self.cache_old_ptr(&idx);
self.ptr.clone_from(&idx);
self.update_cwd();
}
@@ -126,6 +128,7 @@ impl Editor {
MouseEventKind::Down(MouseButton::Right) => {
// Select the current line
if let MouseLocation::File(idx, loc) = self.find_mouse_location(lua, event) {
+ self.cache_old_ptr(&idx);
self.ptr.clone_from(&idx);
if let Some(doc) = self.try_doc_mut() {
doc.select_line_at(loc.y);
@@ -158,6 +161,7 @@ impl Editor {
match self.find_mouse_location(lua, event) {
MouseLocation::File(idx, mut loc) => {
if self.try_doc().is_some() {
+ self.cache_old_ptr(&idx);
self.ptr.clone_from(&idx);
let doc = self.try_doc().unwrap();
loc.x = doc.character_idx(&loc);
@@ -189,6 +193,7 @@ impl Editor {
match self.find_mouse_location(lua, event) {
MouseLocation::File(idx, mut loc) => {
if self.try_doc().is_some() {
+ self.cache_old_ptr(&idx);
self.ptr.clone_from(&idx);
let doc = self.try_doc_mut().unwrap();
loc.x = doc.character_idx(&loc);
@@ -219,6 +224,7 @@ impl Editor {
MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => {
let scroll_amount = config!(self.config, terminal).scroll_amount;
if let MouseLocation::File(idx, _) = self.find_mouse_location(lua, event) {
+ self.cache_old_ptr(&idx);
self.ptr.clone_from(&idx);
if let Some(doc) = self.try_doc_mut() {
for _ in 0..scroll_amount {
@@ -247,6 +253,7 @@ impl Editor {
KeyModifiers::CONTROL => {
if let MouseEventKind::Down(MouseButton::Left) = event.kind {
if let MouseLocation::File(idx, loc) = self.find_mouse_location(lua, event) {
+ self.cache_old_ptr(&idx);
self.ptr.clone_from(&idx);
if let Some(doc) = self.try_doc_mut() {
doc.new_cursor(loc);
@@ -263,6 +270,7 @@ impl Editor {
pub fn handle_double_click(&mut self, lua: &Lua, event: MouseEvent) {
// Select the current word
if let MouseLocation::File(idx, loc) = self.find_mouse_location(lua, event) {
+ self.cache_old_ptr(&idx);
self.ptr.clone_from(&idx);
if let Some(doc) = self.try_doc_mut() {
doc.select_word_at(&loc);
@@ -274,4 +282,12 @@ impl Editor {
}
}
}
+
+ /// Cache the old ptr
+ fn cache_old_ptr(&mut self, idx: &Vec) {
+ self.old_ptr.clone_from(idx);
+ if self.file_tree_is_open() && !self.old_ptr.is_empty() {
+ self.old_ptr.remove(0);
+ }
+ }
}
From 8977f04b1cdac14c0c0c9bd261fd71e792fac15c Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Thu, 5 Dec 2024 17:18:02 +0000
Subject: [PATCH 38/51] Added mouse interaction with file tree
---
src/editor/mod.rs | 2 +-
src/editor/mouse.rs | 39 +++++++++++++++++++++++++++++++--------
2 files changed, 32 insertions(+), 9 deletions(-)
diff --git a/src/editor/mod.rs b/src/editor/mod.rs
index cb520b95..10eeda6f 100644
--- a/src/editor/mod.rs
+++ b/src/editor/mod.rs
@@ -446,7 +446,7 @@ impl Editor {
match event {
CEvent::Key(key) => self.handle_key_event(key.modifiers, key.code)?,
CEvent::Resize(_, _) => self.handle_resize(lua)?,
- CEvent::Mouse(mouse_event) => self.handle_mouse_event(lua, mouse_event),
+ CEvent::Mouse(mouse_event) => self.handle_mouse_event(lua, mouse_event)?,
CEvent::Paste(text) => self.handle_paste(&text)?,
_ => (),
}
diff --git a/src/editor/mouse.rs b/src/editor/mouse.rs
index c31c7f44..a5ee7a7b 100644
--- a/src/editor/mouse.rs
+++ b/src/editor/mouse.rs
@@ -1,5 +1,5 @@
/// For handling mouse events
-use crate::config;
+use crate::{config, Result};
use crossterm::event::{KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
use kaolinite::{utils::width, Loc};
use mlua::Lua;
@@ -13,6 +13,8 @@ enum MouseLocation {
File(Vec, Loc),
/// Where the mouse has clicked on a tab
Tabs(Vec, usize),
+ /// Where the mouse has clicked in the file tree
+ FileTree(usize),
/// Mouse has clicked nothing of importance
Out,
}
@@ -80,7 +82,7 @@ impl Editor {
}
} else {
// Pretty sure we just clicked on the file tree (split with no atom!)
- MouseLocation::Out
+ MouseLocation::FileTree(row)
}
} else {
MouseLocation::Out
@@ -89,23 +91,26 @@ impl Editor {
/// Handles a mouse event (dragging / clicking)
#[allow(clippy::too_many_lines)]
- pub fn handle_mouse_event(&mut self, lua: &Lua, event: MouseEvent) {
+ pub fn handle_mouse_event(&mut self, lua: &Lua, event: MouseEvent) -> Result<()> {
match event.modifiers {
KeyModifiers::NONE => match event.kind {
// Single click
MouseEventKind::Down(MouseButton::Left) => {
+ let location = self.find_mouse_location(lua, event);
+ let clicked_in_ft = matches!(location, MouseLocation::FileTree(_));
// Determine if there has been a click within 500ms
if let Some((time, last_event)) = self.last_click {
let now = Instant::now();
let short_period = now.duration_since(time) <= Duration::from_millis(500);
let same_location =
last_event.column == event.column && last_event.row == event.row;
- if short_period && same_location {
+ // If the user quickly clicked twice in the same location (outside the file tree)
+ if short_period && same_location && !clicked_in_ft {
self.handle_double_click(lua, event);
- return;
+ return Ok(());
}
}
- match self.find_mouse_location(lua, event) {
+ match location {
MouseLocation::File(idx, mut loc) => {
self.cache_old_ptr(&idx);
self.ptr.clone_from(&idx);
@@ -122,6 +127,19 @@ impl Editor {
self.ptr.clone_from(&idx);
self.update_cwd();
}
+ MouseLocation::FileTree(y) => {
+ // Move the focus into the file tree
+ self.ptr = vec![0];
+ // Handle the click
+ if let Some(ft) = &self.file_tree {
+ // Move selection to where we clicked
+ if let Some(item) = ft.flatten().get(y) {
+ self.file_tree_selection = Some(item.to_string());
+ // Toggle the node
+ self.file_tree_open_node()?;
+ }
+ }
+ }
MouseLocation::Out => (),
}
}
@@ -186,7 +204,9 @@ impl Editor {
}
}
}
- MouseLocation::Tabs(_, _) | MouseLocation::Out => (),
+ MouseLocation::Tabs(_, _)
+ | MouseLocation::Out
+ | MouseLocation::FileTree(_) => (),
}
}
MouseEventKind::Drag(MouseButton::Right) => {
@@ -217,7 +237,9 @@ impl Editor {
}
}
}
- MouseLocation::Tabs(_, _) | MouseLocation::Out => (),
+ MouseLocation::Tabs(_, _)
+ | MouseLocation::Out
+ | MouseLocation::FileTree(_) => (),
}
}
// Mouse scroll behaviour
@@ -264,6 +286,7 @@ impl Editor {
}
_ => (),
}
+ Ok(())
}
/// Handle a double-click event
From aafe0136a6f9b4f460ec27b6642290e8fd5b19bf Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Thu, 5 Dec 2024 19:29:03 +0000
Subject: [PATCH 39/51] Removed need to focus when clicking on file tree
---
src/editor/mouse.rs | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/editor/mouse.rs b/src/editor/mouse.rs
index a5ee7a7b..fd829cc9 100644
--- a/src/editor/mouse.rs
+++ b/src/editor/mouse.rs
@@ -128,8 +128,6 @@ impl Editor {
self.update_cwd();
}
MouseLocation::FileTree(y) => {
- // Move the focus into the file tree
- self.ptr = vec![0];
// Handle the click
if let Some(ft) = &self.file_tree {
// Move selection to where we clicked
From 6d4e58c2a9d43f136f23bd8bf80c1a1dd5fb3bbf Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Thu, 5 Dec 2024 22:31:07 +0000
Subject: [PATCH 40/51] Attempt to fix cwd and odd file confusion bug
---
kaolinite/src/document/disk.rs | 5 ++++-
src/config/editor.rs | 4 ++++
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/kaolinite/src/document/disk.rs b/kaolinite/src/document/disk.rs
index 1b46fe94..cd6b692f 100644
--- a/kaolinite/src/document/disk.rs
+++ b/kaolinite/src/document/disk.rs
@@ -53,8 +53,11 @@ impl Document {
/// disk errors.
#[cfg(not(tarpaulin_include))]
pub fn open>(size: Size, file_name: S) -> Result {
+ // Try to find the absolute path and load it into the reader
let file_name = file_name.into();
- let file = load_rope_from_reader(BufReader::new(File::open(&file_name)?));
+ let full_path = std::fs::canonicalize(&file_name)?;
+ let file = load_rope_from_reader(BufReader::new(File::open(&full_path)?));
+ // Find the string representation of the absolute path
let file_name = get_absolute_path(&file_name);
Ok(Self {
info: DocumentInfo {
diff --git a/src/config/editor.rs b/src/config/editor.rs
index 12e340a1..9a327996 100644
--- a/src/config/editor.rs
+++ b/src/config/editor.rs
@@ -653,10 +653,12 @@ impl LuaUserData for Editor {
);
methods.add_method_mut("focus_split_up", |_, editor, ()| {
editor.ptr = FileLayout::move_up(editor.ptr.clone(), &editor.render_cache.span);
+ editor.update_cwd();
Ok(())
});
methods.add_method_mut("focus_split_down", |_, editor, ()| {
editor.ptr = FileLayout::move_down(editor.ptr.clone(), &editor.render_cache.span);
+ editor.update_cwd();
Ok(())
});
methods.add_method_mut("focus_split_left", |_, editor, ()| {
@@ -671,10 +673,12 @@ impl LuaUserData for Editor {
editor.old_ptr.pop();
}
editor.ptr = new_ptr;
+ editor.update_cwd();
Ok(())
});
methods.add_method_mut("focus_split_right", |_, editor, ()| {
editor.ptr = FileLayout::move_right(editor.ptr.clone(), &editor.render_cache.span);
+ editor.update_cwd();
Ok(())
});
// Searching and replacing
From 58c957b9b5fe51bf26f048e6fb120b764b781ef5 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Thu, 5 Dec 2024 22:33:32 +0000
Subject: [PATCH 41/51] Further attempt to reduce issues regarding cwd
---
src/editor/mod.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/editor/mod.rs b/src/editor/mod.rs
index 10eeda6f..66d281c7 100644
--- a/src/editor/mod.rs
+++ b/src/editor/mod.rs
@@ -380,7 +380,7 @@ impl Editor {
/// Updates the current working directory of the editor
pub fn update_cwd(&self) {
if let Some(doc) = self.try_doc() {
- if let Some(name) = get_absolute_path(&doc.file_name.clone().unwrap_or_default()) {
+ if let Some(name) = &doc.file_name {
let file = Path::new(&name);
if let Some(cwd) = file.parent() {
let _ = env::set_current_dir(cwd);
From ae58bad0d8c2a96448342b378822505397b307aa Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Thu, 5 Dec 2024 22:36:44 +0000
Subject: [PATCH 42/51] Added keyboard shortcuts for focussing splits
---
config/.oxrc | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/config/.oxrc b/config/.oxrc
index ae81a5a7..f72ce6f8 100644
--- a/config/.oxrc
+++ b/config/.oxrc
@@ -276,6 +276,19 @@ event_mapping = {
editor:macro_record_stop()
editor:display_info("Macro recorded")
end,
+ -- Splits
+ ["ctrl_alt_left"] = function()
+ editor:focus_split_left()
+ end,
+ ["ctrl_alt_right"] = function()
+ editor:focus_split_right()
+ end,
+ ["ctrl_alt_down"] = function()
+ editor:focus_split_down()
+ end,
+ ["ctrl_alt_up"] = function()
+ editor:focus_split_up()
+ end,
-- File Tree
["ctrl_space"] = function()
editor:toggle_file_tree()
From 134a4f42a86f0c33ba2ae7be0fc427986e577b18 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Thu, 5 Dec 2024 23:02:09 +0000
Subject: [PATCH 43/51] Fixed bug with wrong save as file being used
---
src/editor/mod.rs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/editor/mod.rs b/src/editor/mod.rs
index 66d281c7..f6e089af 100644
--- a/src/editor/mod.rs
+++ b/src/editor/mod.rs
@@ -286,8 +286,8 @@ impl Editor {
self.try_doc_mut().unwrap().save_as(&file_name)?;
if self.try_doc().unwrap().file_name.is_none() {
let tab_width = config!(self.config, document).tab_width;
- if let Some((files, _)) = self.files.get_atom_mut(self.ptr.clone()) {
- let file = files.last_mut().unwrap();
+ if let Some((files, ptr)) = self.files.get_atom_mut(self.ptr.clone()) {
+ let file = files.get_mut(*ptr).unwrap();
// Set the file name
file.doc.file_name = Some(file_name.clone());
// Update the file type
From 5e268d61be9f530596b4c88ca7495c29f84dfbd0 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Thu, 5 Dec 2024 23:05:00 +0000
Subject: [PATCH 44/51] Added a few comments
---
src/editor/mod.rs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/editor/mod.rs b/src/editor/mod.rs
index f6e089af..7a733cc8 100644
--- a/src/editor/mod.rs
+++ b/src/editor/mod.rs
@@ -284,6 +284,7 @@ impl Editor {
if self.try_doc().is_some() {
let file_name = self.prompt("Save as")?;
self.try_doc_mut().unwrap().save_as(&file_name)?;
+ // If this file is currently unnamed, give it a name, syntax highlighting and a type
if self.try_doc().unwrap().file_name.is_none() {
let tab_width = config!(self.config, document).tab_width;
if let Some((files, ptr)) = self.files.get_atom_mut(self.ptr.clone()) {
From d4755f86f79ce004d6f72ce221939d34f73eb4b5 Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Fri, 6 Dec 2024 00:27:06 +0000
Subject: [PATCH 45/51] Update README.md
---
README.md | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 0c980cd7..a1467c09 100644
--- a/README.md
+++ b/README.md
@@ -11,8 +11,11 @@
The simple but flexible text editor
+
+
@@ -28,7 +31,7 @@ Ox is an independent text editor that can be used to write everything from text
If you're looking for a text editor that...
1. :feather: Is lightweight and efficient
2. :wrench: Can be configured to your heart's content
-3. :package: Has useful features out of the box
+3. :package: Has useful features out of the box and a library of plug-ins for everything else
...then Ox is right up your street
@@ -46,8 +49,12 @@ It works best on linux, but macOS and Windows are also supported.
### Strong configurability
-- :electric_plug: Plug-In system where you can write your own plug-ins or integrate other people's
-- :wrench: A wide number of options for configuration with everything from colours to the status line to syntax highlighting being open to customisation
+- :electric_plug: Plug-In system where you can write your own plug-ins or choose from pre-existing ones
+ - 💬 Discord RPC
+ - 📗 Git integration with diffs, stats and more
+ - 🕸️ Handy web development tools such as Emmet and live HTML viewer
+ - ⏲️ Productivity tools such as a pomodoro timer and todo list tracker
+- :wrench: A wide number of options for configuration including colours, key bindings and behaviours
- :moon: Ox uses Lua as a configuration language for familiarity when scripting and configuring
- :handshake: A configuration assistant to quickly get Ox set up for you from the get-go
From 54468c6c386c7be42302efc2b9740763113435db Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Fri, 6 Dec 2024 00:43:24 +0000
Subject: [PATCH 46/51] Update README.md
---
README.md | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index a1467c09..1424e9fb 100644
--- a/README.md
+++ b/README.md
@@ -11,15 +11,12 @@
The simple but flexible text editor
-
-
![](https://images4.imagebam.com/f5/2f/71/MEXVOVO_o.gif)
+
+
![Build Status](https://img.shields.io/github/forks/curlpipe/ox.svg?style=for-the-badge)
![Build Status](https://img.shields.io/github/stars/curlpipe/ox.svg?style=for-the-badge)
![License](https://img.shields.io/github/license/curlpipe/ox.svg?style=for-the-badge)
From c349eded15a18f9b3dc61d16b90cc17d8edd17da Mon Sep 17 00:00:00 2001
From: Luke <11898833+curlpipe@users.noreply.github.com>
Date: Fri, 6 Dec 2024 00:46:26 +0000
Subject: [PATCH 47/51] Added showcase file locally
---
assets/showcase.gif | Bin 0 -> 6477090 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 assets/showcase.gif
diff --git a/assets/showcase.gif b/assets/showcase.gif
new file mode 100644
index 0000000000000000000000000000000000000000..24c369f3543e9ac9b0eb5ee950c82ff45359a822
GIT binary patch
literal 6477090
zcmV(&K;gefNk%w1VSopK1ox8wLPtzHJ5*U|b$EA#GBiazJW@3^Qj?&rE-y(nHB31<
zQE@(Hh?STvE<>!HqaJrytg*MbwZtuyc2lfsGOmWz)!4(t$T2cSGBiXgEIcPDHYX=K
zEiFMZGes*cK`g_HB-4f1+vh1MJTo*(<=@>XC_FDPMlv!>AaaXmnUFFwO)4xzCn-QP
zGfq0iq9!OiA|y5?COIuGLP6q~>gwz*EkY(II!`@?j&q%(n!I9Tl#5(+O=_ujh^c;q
z#e`g~r-oZ)lvuchav>ozCMG#of}5wY&~$E0i;{D(r{|5G#U>~^Dl0w9v#F|jq_K&P
zEG$D`kiWys;*)ey!mVtSch1F}n;suAr_-9WYlN46ye26-Dl0rCDK{o3IVB}GDJncE
zD?BkXMWa)NrH#=aA26Pg>K-65n}~;{pJ%$6w;&)fCnr2CEkZLiODruxC@4E6B{(N2
zJ0Bk}D=R_9bdHg&ka)GjA|f(%lGA&tZkD3lxxCleje;a5IJlPA=bMxuBRCu)G9n{0
z8XYkb7bYGaE*u{)kU~wS!NYwglq_$;93C(e7$+qqIU*u778)xV9W5o2trHg}AtE#x
z8!N9(R~a2H9U?Ls9WH^g+Z-M*d&{IGCpmAtjC7>Bak8nUw&4^QClwkgA|o{(A2Syl
zE0-^>rZ2uDBR0sEd2*BJki(7@87Y~`^TwvxhsbTK$K2PVk{=;5->{z}BQ<}>=-b0~
z;-Pz+;(946JtihNBqTPjuZE>9U(K=!n__JG9@NB6&flU9W5<)(#lm^A|f>`
zEkm@+x0%MW6&WZbB{(7?GfS$mCn-HCDLp5s#~K_h(PgD4Cp+}?_1CIvjN73U7$_PY
zEXmdNZ;|NcwsFg*_w}cNuy^Df9x=5r&KVpmAR#j$Au=E$G#DH#A0ad$A~hl-HYF!H
zAR;pv94-F-{v+{_gr@M@qfCCF2Ot`S&!-x|rUd*_$({Vj%brcUw(Z-vbL-xH*)Q+k
zz=I1PPQ1AB|M1pd9wy}S4C;KPgmA5Xr#`Sa-0t6$H)
z{deE)UEXOg5>3lTbdXWRy};S*4XzVwvTYT6XCrmSBb{=9pxbY37+|rm5zd
zY_{pZqiaYU-(|rmE_ythVavtFXrZD(kGY)@tjmxaO+suDrHt
zNCH|2plPRi^=j;~$R?}ovdlK??6c5DEA6z)E=Fmk4_=xnU{M7ifDSqYP(T8l65H0e
z0;DT|xK>f??z`~DEAPDY)@$#*f&wcnw^Pu)jTcEbX_REw2T9BbK8Crx(mbU^AOz;mYs~jxC32&_P
z&OG<*^Upy0s&C0mDS&d#EyuhS%qDB4L(M9?jPp~gE_?tVMr7TC07KgP#@9P1&_mW)
z^N|DBL8qB!(MtWTG7f?hZusO->F|ftTKy1g
zx=$xTdsM0x&;uU_3?TdfIT$g(U#1o?Ko47oyo3P9FDXC|2cW%i0ClT7fD}epPx|-Z
zhc7bCmm^$;L;)3R>`j7>t$a;3q+$2!ITKz=68za}(6Kv0TBb;9MoJXnwFaUbddtMO#m+-Fx^Z;z6N>|t7g+1#DKnmN~VFss2
z#VT6yROKrS2C{a5E_%_6#oJc=>@&ZnyfB7INCN(n(6W>X$#36^90PSA#}WLCjxiIQ
z(gFakgXNBO0`nFg`<1SO3F%*2U}FkBxUND%ERHk8zzd~zKW{v+fE}FR6*tMrPI{7j
z8${jz5}8Id{y~u`!{ks3*2sfF(qskG+#>DR$U5?|j*eqtAYmB8St=z}*h`-jq`(J0
zNa2S@IKbUV6@cm$sbNYgSH75s#AmV~OCwlb0hh45c&iB{ox1b
zB7+(`_d)y(RCbo!9M@iW$`Fn#kswS0L|^DYHJWg0mfKt!F_lmd{sEz@Od7$!#Xnyr
z<()XqsZMvQ!J+X@f+%C5NdYR*lg3n~AY`dQ)d*1xpv;sqoZJsox;b$@ZK0|JiY{FU
zu&weARdYK)4x+fcZScW|QWTZ)a0oI#C@**;nC1`_u)OBEb!jBWG%mGMtR06nK@b2jc)lzSn&-5!2mj^v(y0(jv
zbF0>%$F&3>R>CY8K=nM#!*ULIYm9B?VYL}5bCxr$*^RM`XN)(+{79agHH9|YKqCAa`e^72Bi;UXIC9Y*GyR#cr%Q7QJIjYc|
zD(yIG+(_crh5Pkfss7v90c=AG#oI;>0$|`HGV^SWeI5ha8sQ)&c%#&$ABc;DJx_a6
zY=TX&xT;4k5kt6~IZ!73UY&!zme`swW_7UROzdK_GqZx3Cucp&WB2%2lo5O_3>8U+
z2Uh#JlT)0^l&pZ81I|r3isM^S#`^Hw5mj>
zEM-`FOLGXla@x^O+IGKtf$?^n0_sd}ESo*acweQs%QeZc(=h6YPrTw+HE=Ud`ww0}
zCD{GULWc`l;`9cbxLPY!a_84uiEL^vL7o7SQ!Q2K6sFk{3`MhgkLBAE)XTSN%~U^+
z))H&5UUBZsC8;=K=sFwlf9?C;movdFfAQ-F!Sv-X{`t_~m}0K=tEL+T=*-q)_Qi!H
ztV>7h5+rT^2_yHnL}FWVvV1B;?JI!R;^20P+$c$AeW1b_CeRZUz+o9
zCL{n4H*e_%6zr#H$Td)FwommFeGnLdQP+45Xfm>MIpmjQq&IJscR6M7Ec6uNGH#RfBOQ
zgtg~uL_rStOi)11}ZM*Sk5PbTDXPOGIiW?HFOh(S#vjuK|BdSBwge@
z-uHc&0&OZ3aq~4eQDb-r^Ff<)N)Tm-3#E3Z)Kt0S4c|sekXM3f#&`2WSsBPz|+*ji^S6
z$SDT*Mg>4X`esTH)HshONiTSKz%*dJCN9JSJPgK6KUhS@16ahGd;i}f`L){IW{Skicn%y?_Qw{*kiiQ+hpm^FPJQy82!JnA?+!-yE(A}D~dF5S0=
z&jEg-<%p$pGonRDgM?(X12AL-g1gSbnVY(~o4nbZzWJNL8Jv+a
zoWxn2#(A8`nVibGoOR+;ULu^iiJQK_8K0olhUPh$^qHUf=^*TR8t%ED02-hwBA#y`E|n>u
z;W0n$iJ!)iC7$sluHm5Ef}j+tBK>I^{|Q@
z>Yog{PMWfP*{3H0!=QR%J&ut9KPQ!z;h+nXp{3zeg>)qU(xIH;p@AU~G#VO2Ivd=#
z7)zR>B6FcX+M?#8TJA|u;c0f`G6xNsqXZxtI2xqfVxt^dPwfb$Xo?`zhXLuNK4wKX
zFqfwFXrGwDC5hpqLuz$ZrvXc;Vr-aO;h0ZL?~
z^qQ@tI;+H*A)Y!J*{VhO*{lZpt6Fre6RIceYOf6Xt@I_iLn0yw6!Ftf&oNC`!9wBp-IcMdMc)pB&JYnwa`lV+#`nR)B`js
z0K#X6zd~y*7rZk|r*-PMkK44&hPQeeS6K?VW=B!v0zKRR^mA8BsV{o8uiL$UL05J3
ztU7nJ-g~u0}C1ilMXz)lFg8WOMq
z+pslyy#~y_=eRKadxi;cv|F06IZQtgtia!!zVsVSJp94vd%zTXx`z}u;=95F?7m}b
zzOhRoPwc-6@VY5{#A2&jZ2Lwp%0Z4)zf$bNW-J}M+KruwP8u8-esv^z6^}@#t!GTD
z(mTEJdb*{$r~8W-*P1Rz!m;wGx)b|qiE0=lJd9-j?6#+iB>l>#LPEkgT(LF$j*l#r
zOe?u}>#W|2r1;v#@2bT!EToBQ$^ToqC04(E49On*x9aj27d)}&o2)6EDTVBz>H43P
z8;^gSeM-`=fXuAUBe``fqKu2jd8{dsd&qA~zNfmr!OY5&9H=^sHjjhFhJd}2yvPI_
z&9h6%XFAO|Jk7g&twuv3OPS7DTn6vRr_3D2v6snWEV>(|&U9SN_#7i7%Pn=2W2f3#
z9&YM2e+Efok6q5YP0i7B)c3KHN3s2Dn{NiyM?T(uk6RK3cA8_&Vx!%-YUpB
zWVw2*r=;w~=d>+{3b}&vbKE)yAw9p>o1Y~d^(}JwhrQF5{tspQg8Pr?7%txsaP08lN(F4rD&+O8kJJMmD
z(r<9GN_nL@#J|?cY&jd%OC65~?a}8O%(L6h=8Uqc%hIAu)W~DHRXy0Zv8!QJ7{uea
z84x^fTy%y20LA0jgv}Y{*|-c%%O#!JJv%8oJB<4a)VJ)fi5$rHWY^ON(x=R^vntDt
zti>@M!S8&w`Qo~L?an}atKu89znZfUP0=`A&nex?ehS=vBG`0|t+@=;ep<~vIKHQQ
z$PXRCP)pjaoZFME+%QemlRYxWjLZ%Ht;{6N+pyTcSDt2O3byb
z$;Ms7gsrf1&+L7+
zqFdJETFt+`#B&Z#&tv7fJ=f3wgWpKIzF`ieO7hg!oOF1L;(tBqpM27HwXrBE|r{$6_qT>64+*pL*Fvn3
ze%wLZwerN*Q)}qL4&Yu4l~7%~LTj+Q?(6w$!Y95jiO#-Ao!?4b?6PathLP(*y29Bm
z)mvNUBgEz5Zr0fS$(qjU^l{aO%H{&z<{e(SkXzG>kwea&(Mi5cG(EOE+txu##lTLw
z)h@bZ-s?^I-e!I5*4*E_ZodT#mEw-!KRp7_JVcOs%h4
zZmW*bs6VCh6U_3lE>A80?Bpch!Y@xWz)B)uF0SxO^BX+m!>n_Zp14C{q%8k29$KI`
z&o9Xe^FzoaGp~K=OTp{g^il8hEZ>A4jPowfT~b1PEkE+KspeDqq9|Y2BpvfIzx15l
ztwIm>FF*4Qv-T+dF=~i{rea9F&`IJu(zv~?$
zulknB`JIm)1}f-2lKWjl`@Y|$`~3R|&iZ99{KgNZwqK;4fBeiJypG@eolg9n?)uPQ
z{aBa$%5VMJ551oMzWve9$YtvN;*Xx#Z`ss8{^%c{&kr~4{;6%n=^DJ*iI#zGXvnq
zhzpAxB%sIth=I0^3}~I3?|{yve^(uFJ9iHw!4JOvt^6hi*os3>r(WIqb?lOZ0vP&I
zz_uO-_zW;|fPC@fy-mKxO;Ble`0*_(@akFd=!fd(oBru`HNlTc3g7_2Oz45J9D2y>
zH6s-4AqS~2C{8Kk5)j~k-2?;RfF8o*O@Q$*8qg{EN;L6A6jM}jMXt1PPNuq+z)ztW
zryGGk(r`S?Fo6X4ucr%vt4o00lzL2{0(jIQ0R=xh2r%Rne6gqVjBKwXC7I+cA?7fY
zQa_lwoRUQ{%QW*$6}Rioq`f|BssP3U5MWG(l1u<4#XRB>xA6^Upu&Q?e!*Y2%Wk
z3$DEX(=IXK9c*5UVx8(<
zLo!H}QCkh+)vloyRiNLh27%b#0>tgnxMMTccw>$=;!Y+3ap~+8
z+mMe2>Fjvs$OKguQf3Fz0f(?Iz#&2%e!u~T)e;zb=s^MGp~C|u=Hclmq5%5orkCLV
zG><5X(C}#B)q-q$qu^Krfbc2z;>8!W
z14CAMi4omSm}bNiSA6jllhtWit~e5a=YoHhcxN1bC>nDdlD2DUvLocWhqX=6f$F!f
zjsnNhp7z>sw#7A_>#eDd;xx74LXw9(@=(p&u{(S_fUK9s@r||@vlWA=2wGUW!3Ich
z@51>Pp
z6?nfxTiMNdGGkl;4R}D(2t_9QgItf?SCRXDMSe^Am0hCJKm=xxCN_H=S1^-Y#_K2OAVa}oTyY0eMBOl2F-XzG^Q~jj}jC
z+MVupXSSyZ@h>l`vqr$3#(=O+yjGA>#PW
zi$-cyaVB^;cg9qvGbI)qZBi%?a-^Pm(ZW4@md}0mvu=f?%?ScJFoG7cG}=N^r#`Y!
zacMMPPy0wg_tyeKo~B?sBuEh~nVSEgt&>x#m_U(a(&9u+kqU$xN8Ptpl#NDv#-xkg
zY}uK+&T1(DV5IpV#8$kmvV9qWEBT}|!34Zg1Mj&jlK%Qv0CWidBIjdFjkM%HXn~2Q
zo8|18!Wom0t;s#gpC2x7F
zV^f-B0E4WF?cL;9TP@^sNm|hD9V6yE9RUuwPZO>|byQr{=)kM%1?XA?tWV-GwYtu=
z@Py$%Rp3!q@W2Am|k)_NHGEDKJuVA
zmJ__yZJO3tV9lw+F0Bi6jw0Ce0YJ0nC32CeLjZyaNM?2aY?qW0mRh@z(#UCaEun_&
zlA!hYwtnH~N4Bh=1W!ehItDWP(aUamx_X3+kvCfcfM5ZT
zaA*16KfTh2!l4l+aCavegm-?$Vj}
zF46)>WNaVrkCHuPV9_gFHc#76J`+_ERM200U!N{
zWb3!8phS-;Bvx41m!On(w_Ng*msgV8m7t>t?mWmTqb=Q+UEmZ)ZjZ;1dH5Ex^-;cK
zUGdy{)cg9FyBjt9B$}lIkhvdNdN4r~TI&b@Q-rAT=LE0&oa=O?gQ)2)07W1h@o7wY
zwUTZvLsHA?I+GTx@s^581pLJT4L5egq3Im;7y;y4ouebZ-7&rbNx-6EzOFHh=j*G28JnjW
z9<@*n{z!|c(Z2mrK#H&p>l2au0yI&&E<_2Ri?OWqs}Pn+kl_grvs0D(nw0_p9~#t<
z^Qpo4D<^V#s500elB$sNF#;JB5gN3($hxm20yOeLDI}pmBRs$Eh&5x%xcl3}`a?1;
z(!YMmfIG1kF}$xs*&>z7CW(0%eaS5UF3cF#W3)zNn4Y0Svq}_M(+KFHIQFR)Kj6bZ
z9GOcILeb(sm5`kls1~Lw14~iFwr~kUWHX8xLqkFoN@>H5>yC{=5=)%I>vi2B2}Sj4j;L^t9lGK@u7%q&5a#XvF?S3Il8VV}Y2M5J;S
zHhh>vv@A>XB4n~GOl+Dt94KRyyJTc9)cB&&xT{S}MO|bcVAC*B497$Jtjvfm9uvpJ
zS+}f+Jalvj#K^;Tj7NEFoJA`Oc3in2kuNgm6Rol!jD9SoFaZq4b0UA7
zM~3Vya?A`knG@e@NI+3Xthh)2iku38%t(&xNXa0@eT=qrlM{XHk8D$tFJwi_c#XD^
z6*C+OiDWjI@<^J@pogqUmb1uG6iJ-yNuT`5oB+xG;7On?N~1hVt++{~oHCsZ$)$Ws
zsEo>osK>3qNU6L^tjx;gusqizO07hbrX0$z980q7NTKuzt1L^kTuYkt!i|KQBHg460d
zCW8v#;*oB9lJ0{|2;xisnDffexXZ~P9{vI)x(kyOtGF+T6x{$q&w!Gpn~L)xG}6eQ
z9>`0D07rB3JI$O57JSSJ0mwe&CJ$0L6ROwzo_&is?t&`;;APH)5?1O3R{6B^9Cwwl95j#^Outk8^+O_=0^@Ys#=*gQJ}
ziW;e&-t;Fmw9u0fPW`LHoU)Ed+E1&1x}w1rgi9}%sulgbkR=;YLcvie*-W)7#SL-0
zlUdF+g2sjL&Y9c{DOs?~OVTw`ELI8*ssJ-+uP
zanTdO(NkfP11U)VyEG30I7u>{iJY4gelkxQAsUjHoEd==9sN>2T@kljAZr>!JP||1
ztgvmn8HL(_nSh9XVB&K#Z8lm%uF0-H23fbre(Wx4Ntoc9IcI
zaSCMBqUb!5jN;ZLX|_vu2zNYQ%`!H5$!PZy>
zm@NHM0P#=%E3?#Bom4mURLJy*Tn)`=O;}lLfmuz}RaKO8eN~lZM3IFSb@^0eHPu{|
z)?cI%YxUB>y4QaZ!|klRVeOfIlUXe)!}}AJcS*2?;-rLk^ee0i$@LB`_`I;0-8?wG+!9C$&&a9n5*!slgbUohh0E
z`~lyy4XE*>)mfUU0lw9^A>ZMxv^X6dX+m;p7#$#+h-)26LeA>@iqgQEB2cENS;W#1
zuGe`c;94adi&A;{9x1J!3)w+nxuw$zH7rcf@G76_5}(yYsE=YArw9_^F$rtc!OSDO
z9yBfg^_mb=iZS@94F)J%4Iy2Lk)Ck9m=ArXC=4mIjiS%AAchO99y^fw;SdHOC6-xG
z+Ymi?y&${N*Bg@_77MKF+^Yl$ukIVz3n@_z2;T=`Uk(AEqazRXE0~3KmFcA>`i-J|
z`AxohTfGd^l32~Tl`RpvTcS}^yVX5}NQ=I8p}$?O*%917w2i{`FT_k&xO5gQ*Y$Zn4iJJXI1yTDm2GrQD_=54`}q*ew`c7MNfr36Be`
zjo1*n=w6U8RDSdkK&NBV5OT|E~#i9LTK}%8>6KL
zp^8*7ka7YsgZZIYo8@HQ6C>TeKSkh{7*n0XdbH5&0k~KaLdGHg*$G69
zQ8|j3BojKUcIRE=od2(wADxk8p5$pVn-ahJLKFdTM}qYJ;lSi4;Q
zL$V#7`5opwE)RHB*1E|jAE^%k(A&eXJ%#3Dyqe;toxL9^Wohq3VT_3;IPq!B7VUgaWfrTY8zr_`LM`INN`jUP
z$-Ltp6CoUsS}7))JDO{7G^(hUWW4sTMZBR^?dyt$Y!g@=zD7O?O0!BP2*mCYhH_!S
zer#)u9@sU~=ssS&;t+AW73^&37ZZ!Tk+RvuVl9}ru;8av1@R?W@3$U|0d(q8d1`MQ
zrZ}x?7k|vF&Svy%!ID5<(EVycyXPA=sInezkwNRZ9!^a~+MXG}$&s%S+O3nF;Bjh!
zaXQ=^0B{;_xH&P*HF^uPx}k|MD#Kp30ymIBS!~I*Q|q~23=n1i$089EATjM~?i(g^
zD-AZn`fO#Eve9M$(K@N|G@sPIK0Igb+i>kF{|bSL=7BBG+TJknjPT%qJmG$cXbOw!
zy0J&Ev2yDWr7%65PHem~(mk_r4M87T*3v5pncS5nXYRJx)$!X+kbn;Ft{rHLz1ShA
zCxqr-63?if+AS-OZ=kCU)rj)E78m>;bRh&&|E_XDI>7?xG{QP?ss&S=6y|?S|0P1kgPaM=piU^>A-$%%WnFP;q05_7{J5?TB%6dQeVHE_m#oyPQ}B
z(C~Yv6&$N=A|Vk!v)Z&)>oe>TEl|`iLwGMst}>fDfL6Z$Qq$ZtQ}6{posm5Z2urXq
z^Mfx}?DBCWJ|K?~y+j
z^>YKV?oKwBL7%>A&(OYS?XC1moR%2wZW@HNO5d~RNtF#LNG9p@Mo-@_YLw_6^*kGj
z!0a@Ym#NQc<7{@JhJtjN5%Oi
zp{KRC%1QD0KI4LEad?mY1(J7$AR~zcjsiYi!VF#iZ+3O{BoZ^%=Dx6b|2ZupapQ88
z9ynjif-h);em8x>0S}*?eM0IwO;~+Hz8mn7JRURC*dL;6s#SfPg;G~7RwCH7J|BSy
zsXPk{AFmIGO;WN1O*RTzqN7p@JSl3o1xRQs7351Gx&^nq%t896G}m5TLfJRGlNc
zY8_-qqD`?YlY%suGHOCTdok24gE#Hk3@8Kt3^1}XD2BNg*%GuEpvTdNCVJ#(C=_bm
zx-x0z9WCJJOwULKBK5lD<4mqG3&eh!p>TlAskI^mdNkx=zQTtSFK+xe^5n{wGjHx3
zsFtwLm1=lc-1^4=T66@!K}LrL0XS;v_+eAPkD3LG^3@ULh5#KKM^p}3ghv1YJo5Ao
z5T(d^8R)=+dc8?y;7L54^j>~p>1RSoI)E3F0X-1t9zqo&_)!KbVP{c&92S{*-L|V}Vr(MGlCf#K<
z0Wbgq5Ak^bU>r5|m`>RZHj+w&?j&8BjXL@$q{J!Uq?b#4NZh5};ZP++I^eLWf#QKW
zhj1+1zyl9Nnm{Cl1L|-Ct9}?npk1q?8Y=(__N6OCMOYY?si@w-f<>&ZXNZ6ZuG&Ks
zw6aiT05?R@Ux?Z9+7Y81jZ~jvp8aGHp!Ly)QBjd?2%8b-nzYkrWsEB>W(P1ZfNku;
zwP%bW<%$uO060cmuHnwtF234o<)ORVo`%z*p^o*?zKP~LfgTD!A1K*CAUVcQ{c@>=v5;(Fh;2*{-=9#miHk`AJ6aN%O_a@yFq=rc;YHCeZG&
zAg*XrWCB-rENu!XK=htCiq+alMr0c)*Ij$PWdhNCS>2_j)|uU>oF?Gus3)bZE^xOR
zP{yho8sI9db0_Gm38$tSv$Y%gM4{ZXO$gl9L!#PS-AN$~RJDHSNE^!x#~5|f4e@4q
zNl636r-D=GC1Vax2{)v5@j=Y?!fSsTWv%5&NMw0U!k(Ryw;R=FW21llJMh5^KRofp
z8~+>uF1G0$*|j>)o^migjaKu}Uze2i*&n|>_l`~qH=1Q@|9tpon{7V&%iUClo9kON
z?QFcJpFLqJV~W0|BQH1q`1SKsr~mn|XTGd}FC_UJ9|Eb}$;Z9~3UFisU_ia(ceVrSkAywR%n|@#wG1ZkFiSXDa01u88Pd>8VcS}x#K*z%
zNicv5+@AFC7eEt6u!F_P$OBoZ#O6sVh$q2d4WlT1-&`jbJBk-QZgCe-$b;f>j^>g%rl|m)A=h;Ds5u;RDJ$~=
zB)FhhM>{gIcsJ~t8yiW(%Pehs38)G8OfZr_y#bSw{3IwtDauhMWlhTy9#xb_%2l$`
zB9w|$BwJY~1GK3B1w}kvi^gZhTmCMVvfNSuq=d>^nr=%hsUOB{akI
zNUr%Znvis+C6h@_WIofH-uxyw!?_$&inE;N4CE^zRn2pado*EN0EhP3H(8T_BpzKo~pO<-I{idIF9=1zA`|cxEjls0CPi(uq|i!iUX8CI?p`ta?GD&ZncTO(<}GGhh~`|F&X%^#1q!e{SpW&~bg6S(DuA&HI1I;kKY!zpg$H=cXvxR6
z`62QDd``?My2a-PrZa(hUUdQk;zAr&c0MznO?24gWM4@OpweKeo#@HhZLk{tu3C80LA
zAPUVI(0n9xstE;PKVe?NsbHns7-k8hO!T1{f6;CWK*@?
z6t4Ng18RSJ#_ux_8|~8NPQfbM>;a2vt>&Ix!_PmzE&@XIBcTwzq0J}ZfmKrlku?q|
z5A%7p%B=;(+~6sgWW~})`K|MvuM*JBQQ;{&>$~LS<|(kfngQXOWY``(OVAquN8y5<
zlc~inLTAlM`UD#ct`_5NN%4ZAI#T3mFyJ?~2*I_}pL0)$x2KJQdm-X$tGZ9(kLBO{
zf?b65*$rH)z`c3@%Gg<(ixG$c
zblbxL9mSDMTTz+7kws
zW|)94vEC6v)R7dIb_fYJt&8yG$cCJXdfc6=RiBph2XNZYc+k
zT)^j*2QeX-l7J$?xl(EDAhH#dQ+FrOtFhxaE_#a8U#>v!5z5Jskw&C?CU|%>%
z0FVLLK?IUqK#9}BRfrzra2m4gt2I>|!*4+eBMZK-y$jl<>^`HqN%i@V+cFdmf
zVTrl9o$MvqQL&HO(VGS+iPRaRfE*!EOkh4VzBrMhtndPvBp7~OU#
ziAXX@bc_y@7+jqw+G6hK0s333{m`Urig$oVd$J9?(OQ@_P+6XU6ygU9#77>4*
z@+Xewr~x(R{_w$hwM9)}Mr~Rr))<6fP7PY58U=m-416)^dC^^L)|)}_j2tP&wjd2-
zG^s_HSJEhkmR^#6)k=EpRc77hf958LRS71s1cd?Pik%LH5zB+>4TQ;9ovj&G-Kl0m
z1RO4vi!o*5Yz>U9Sf1@jwqQVo;R##3n;W)iU&u#(^hkWXjRYyin|78~>Zqok6f^G9
z0I6x5z3EbA6>x-{sh)ri+y@(3)sVQ@pN;^XCC$;;4Icss6BfX}sR6LV;_Uw5t@s3b}?+mExrm&0D{b`>nf%96q^?8bg9$cCSPVr$5jtjR``
z#u8nD1k=ej*}T$g%f_tCehtKqiNww<&gN|JEURO(*GzQA3K7-Jb}Y>5tkE7VZH=tQ
zG7`nMl=|E((nf7U@$5Jq1XZZTlF}Bu{wos6EYTJ%$4W^GN#acw(;Ez=)F57qs1i%4
z-(98cJOMh+#jR5Hx
z;+~D;zU7{Qm@}Ec;uhvLCdV=1ZQCCIE^guEP$n)po$bhcZV)0EyAe}{WNtuJpy=9d
z;co8ee6HQHZS9ioPo$758g0#r3Dia`sa}VET-TV$Zr=8;xgtko?9sluZnnx+=0dB@
z7S-AIuGDTS)t=r(Vp7zY)t)X=*T(GE+LdPfYmHjc@G?)u%!hqZnnx6!0f{C_NW`FY
z#btySPhhGHHBh8A#ER&cLYxI8Xo`(y+j(IP-f2n1$)77
zrpm|yQ`%!h4*#4LMBGe<5LjjSSGh!vbzY
z>~EyK1y20o9Q54?3k^hUi$`MY?|$U@qA8>-5}SFXU#L#02rEBo1qR#((DKUij+R80
z32RER3PZ~OdP~Ia=?kBp8fR~{rtERtAqG5JN03)Dl8^a81r#07oiR}-6JgW-VEJ;c
z#YPzi?GPM;@&nZ*Fu@hgovqOXtx8DHEuw@>v_`mDT?EI>`k91ieBFVdnQmC^b+W{~
zyn&^i2AX{t3PA>%4Pr-VNp7&o)Kubi=rN)?1qe&CQ{)s?97_=gSa>1Do$$o~_$adU
z03Ym^)>I}si^O06%I?npMF!xDk-jlK+mtrF^3hC13(Lq5I|N?bvdpEoig!
z^EMwSS4iPS50&1AG8Z!uL68FtP|%{Ub9hKYd-5j>Q7HRzO^;Apm5@;DG?cwEd%RFj
zKhRU#q-i-ajzV%q4Mup08VHTw!SKun>52*|u(&7${UM5e`B6q3v!QsA#L30xybvPe
z<0vz)HhqX4CkyVh%?qWC0d`X1tjexZVezuca{dS7+<;0m3hGv6#vvGvu!j5s;s7Q{
zR@jeq4zbS$vHw>8Hi~cyZ$$PYhDZS{ap&NlxGdsX^oM98W)lNT0d8}D9R=R$PY81q
zRuu9eoU_%Av(m(KXFLVM{p-{l+y?r8KPD@mJll%Wo+#Tj3ViVhgy-JnYA>__88
z7|UfJQ5x>BHp`kyNLea&$o0UqxpYwiTnfQ1$%zZOQH=c!t7@cissKPhzrWq-3hji&
zcua>xS8^YXlJ+fJQ|Y3lj!pp5WSJJC0+L8DL;(h1if7t|herZe&P+sbO)E^fz%{Rk
zw18W+Vq*0X{UcXI|EXj02K>FoC2=I8cCni|aXMSH*04kbXGA-r+I0p%c*#Y`MPgOQ
zVO@_FNKQ#K&Kj_`oq2Re4@L-q2tdU3T72Li9~d+y}AT4Ln-MfaC{D1~MR8^3m}#
zUQBjo)AB*|^puy|ia3R$-^N1FW-kv~Ax1a*l(Tg%#O$36HoH)w#4|PX3AYMRr)#=w
z_u(wN)og17rklhbZc^Wt8q;)^n^7N;_JplJ}X^u@N+B=-F7Fsx>$PF1$z@C
ziY84ki|CoL8wNoD3e1r9QG}^Yp%$IUh*V#ax0#vw=W99Y|9kt#CN1k6-3_5xDbO@iwiL|ritV9si
z1(&PZBtJ&JrR&RHGP)n3>$MJ$-1m+>AuIP9can`vo?PR?-Px+8yAZ3M(jR-pOLYq|
zqRX$$8_0H7JH0%*0Ahxx*=H9`Osco~mP$L6Dk2wCMt-J@&{8yc4?Eub>a%mVQ22dK
zEW|>0u+=>KWypAT`-VYq@neLSX^Y6V`%#MLNzcr%$!xI0mirJrqnT@l>)kisZOS)a
zVtC@^U4q-P9V$?LC!QPxbQRn1i4feYEw6+Kj97N2YY|2c;&meQjyiqF<)Lr@!~i1)
z2nrZvD1eb384MjPBfyBkhJyec0MJl?#U(v*6qxuCWXOgLj0`Y|0ii{V8aaAwh~a>M
z0u}-ENboX4#sDjCejKoHqD~A2dJLeL1E|BGHZ=sjC=zN^sZ*&|wR#n6R;^pPcJ=xd
z|7=*XW675NN&o-=lwJc2coVXKfVW#QFz6wmXw#P}Cl=@!5--DsJ!19%8sM*nlLw5h
zJi4>*K*NMxnzeixbLO!Mv_Lj1SSn}@I&d_-fJ34VEn0N!46qspjvqJ_Ko!7)1;`dr
zBu+GOqeu}RQ6S$o5i}1E>EAUhI3ocB$iX1QWV#M3`Fcppr3C8xD6qW(P%oqX
zFiL7B>n7_Bq>~1aiGcv#FbuDS=#y(Qqbd@Rz(L@nsE3GjDuBj=&J*Cg%B(Wa|GJ(k
z(ow$rytA-?=o$;MJDV!XQ6%{c`l%=ftE=%N^-?0xNC$iS?mfy7!ZO74dJ7CcnhyC7fW$KNt;855iqcc7e(G_p?CR7BLJPkX
z(Mqj)HRQ!zk@StG5Nk?cgc1)hqBvc*OX^dF3}Z|K1!96~1TW*&jo6m8YMq+4o1;{u;jEEb7Waq9bGQX4({;oqI%{=lahd_N%pn0o=
z*`{mNtWMVv*n70jpmYuPM3vQK>^v3GOEA_U9sfwe+j6{3is;;!BTj6I;J
zmWoEKq$QF*PsyI|3VAyfo2H6ob(fym%-%q?GBD_juo>t%W-3X`CapAKSsKfpIcm2H
z+)v6*$22j6?NLXX-u^h4IfN<9KA!`LX#V680g$0UBJx8C
z-++VW4B`S7!&VetXaED0p#_@ST@-F`kQ;!^Q751X>|kiJ1mH_u3FL|Obi)_D;Hq&@
zl-h`dbO46rM5_;UAa@ZB%xGz4MBWISmVlqJ
z&Q=3LbA@L=591Om&R~b##Q;#`LE+Ybhz)I_(*f
zlU&khxjG=V_6WI@u}Xv!`PED&$w%?+u2>ohW)>@$fpoRcVn2c?{OsdUXe}Wv+f3$6
zX@baxB@8;N|$hgb>Zfx9~o|KQYw6vL<}G2B5T*ueHXo@OU(
z@x$p>EyEdOb|nL0&FDwqbvhkL%pd}ojcjT-8`5aiMN#Mh59)J*jeX>YI(S{iXcvWW
zhN=i1^9)2d866!7GhUG?ib-zC7{lCTs?#Fq-b@v;3JQ)oUSx`cazK#AWQQO++uiI4
z(*vOnHhX0J&eiVX$)FTcPIdGwW8Z={Z7Gj1Dq8Bhhys~Z88Sa!!^`6)BoU4dZ?tZ+
z3)td<6qCG*Jb@d`Dm$4y#Xh&OP=c&&FIcn6jwg=$+{+`u_FVo{Pe3pL?|5y?N)Q;<>vb`1OTfx@bZ;L^u)WmZ{R>=@#CuodO
zlw5?gW!a6o1yIOX#be&}T+g~InU)*7rv1!==of
zY^b9FEtp&DDA&F%vKNK+8hu*Z0zwI>k=^WOGp}_36g0B$scv|Zop$PaA-2^
z>1wX};h_fIwUZs_7I>VV#=htFxT&r;jJuD+mbIi~N>+S=ltaE|Iicxo(p3j&)o%#6
zs>5v`Vk$`DaGCY4yZvy>MI4}TX_1^7N)eE&oZI7$Aj*Z^bw7~2o?E-<6fa(Ov!9)-
z49atYKGdZT=~F8KvX0ceGN1z${}kmBo&$@sSXo_UenXz`m0@8$#)Z8UNcT#Z+=&&zCr54F_uv6ubq
zP_wXk%>D5>N`n9i94_+Sk@Mk~ry^8(pyIKD8*Z=+vmVf^L
zfAivm_<+y<;G_Wmk1h@n0T++~|IYp5ufQS@_8O1^F{A$MuL3hr12>QZ0r2{U@BTVa
z1V@krOK|;c?_7*P3iyBp|67m`q+s++PzKdS_&6^FXAlQ-PzQJLTLRDkZ;%IrPzZ;R
z2*U*hy8-~Vfd-b437e1xj9>uIBK@SW2=V6vL9hz5Pz$$^3t!>`Z4h%zrVGcA49jr#
z%4PSgLI7w$3cyW324D%l3T}X~mpte7%y3*j==L~H^61d))M+T9u#>`D&95I^v#ddz~w
z>_Y)QEGiD~`${DA|9%l0vk_d5kSjtl>C}xB6C^xdIav`-zE3~fQMrcnW
zZwUah^2(qg$*Lm7uqp^?ak9u91y17NG5Vy-8Bkt?DQAH}U4&+#ND5Kkh{
z05B{dN$eplG9q6BBfEwpLlPxxDbvtIBlmIHFfjo5z(NLK&YHxKtz-f^`;_QjDRV9swN7;G-^z#2ttQCWT@OFtn}-mwo)wW
zQJL-$n!ZT~|0F^HVk5#XfHbg4PAF_+ct;0L3{q}@uI|bN6w|jTEKqJT50WFrZbLbY
z%bO6T)1p8)8dEZF?%3q*Qs^pB0&`MYz(qcjhH}GoChUeNA~HR*G5u=a7A7Yu1F4Q^
zZ}O|O2BqZv>!Zy_`l}2qPvygGPu^FQZciSuz3Xkc0r!Aaa5*jf*s9BN+ogIR5HV
zGLr{-Xm=FzcFIqV)FnIv6*{jxvyz|)fGR`Tq7{X43ZH2r9OeQ@M^Fyrg-F9X9HvPyECAvY
zhN2)3Zh%&9KuPG+Vtze(?TxT=uY!eP8`c5T8l3haW@5*QoG@iHa6?RKag%tn+Zd}Peq@Sb25XdN
zO2)`jyl9+&HtOz`Xh-l;#jz^+upxXDU>tcTNI&$j4+ersR=7u11
z3WY8bAp?JHf_BVve|6|RN(3c{$+b=fBr;B8H+x+J{9qVnurSBn6)E6QJhf?||!&G);sPvCi$clf1-?mg@Gd8ZWj!$bdc8~aD|3kKu{l@#;w2zLp
zoJN2LTosDR%1Om3ocz=r=P7=3qD~0SGqgBG2`zGeEOur^Wb4pYki}l9v6A!o48O21
zLui&YvM+2|2c{``kcEYaw3k6AoNp3{E#)`_YcgHc@@y`h7CJVUHaP^Nk9T5aJ1sw}
zxlH@>Dt+r(j_$EYCY`*my3(a`q6D-Km~lLUMguXqc&TkbteW*%r*-g>_m>8?VX+bq
zam^JiBl$}@OE5$0vzU`G8jDmC$S+{#avem`&V#Bj*n9Pmz&h_vR71fE
zVgs%&B?@#ChjREl
zrin?j|8tl?8(TDmw@Agbpa!Y^q%MWCHjry)ltRc%QMAW<1SL3bLDUJIP!vN$T3Zd<
z25!?*uIZ9;ch>D4olPcnTC2ej{bVwR;M)?(%Kai1F6R3IPqox~4ZH`AH^0mwH*$m;
z3Yd-PIwIB_oymq)hQXzcW>e1A^um1voMy2OAF(b5|H&K0xr($uW5i?VX&-Rh4z|D}
zyXh*NBmwv9OFM_GoT~B6p$*+ya#dQUve7F
z^ZW!KP)8w3lmN*#tVVe9Hga(Dz5p*Ziht5vzd_M;g5!2?k|2DZdZh(g{$@FTKsg
zJQ2xU(?1>5dyt=hFw{%k)ZHx8PaUT?om@O!)n6Ufm1)$+Jl1R7*7YaVZ~d59UC>=!
z*MA+@y%5q_JlKof*g22SAM)``<=Ekb*EP@A|C?RfLp{+oF50IZ+p`^pl07Q`Knk`2
z+_wQmFFEYUHFy9|O%;&u&{XF3z-r})6*QY|xDrVEjXT7KKz#s73Y^~8C
zqB;bQ+tGZ#t$p3Moz$`kzmsT^1so88S=mA!z*BBGBrV8$?K)VXYZKU#9pZ+OS_p8QYOUg
z4}InB@diWZU$gTt5mNyOlQ!feuImc7|I=FTm%}sT`tKXt0E&P-LzcpNz&vedIj}4_
z_P#P##`|=vsRN2pmzwNhgj7lzD2gT{_&`RnV>0|!6R)21_j2m>wdzY>^bB
zJ_o#B#Muaj?7{?uZr46corg&ym?zOHk4XZY&<*}`?VV{oRpIo=W8Z*Ozy5{NYd44k
z=x=D7!IVy=|L4UPA{mo`ZiC-~C=dL&N{Qoj(sXJ9+eL7(pTKC!K4i1O6$;fZiS4nG+K28F0|^LTWeW7B*s)}BY6);MBuJ40b>Pro=mHLo1+>iVs#S{)ojT4U
z6cFV^!4^?;lo>c8N>&y;Zp;Z#c&fk~ky)_Kz{BuB9zCoArmNU1k2gAWuqfhKU;?#>
z@y4wx;6PRYIgHHCF~I5ot^l|e9MB^qhRz}j8~}j$KxYBF(W-V@HoElb)T>*+jy*d=
zjaf^w&d64~`0?b+n?H{}z54ZIbMEZ+ZKhcOIz*A;
z5(4lL1rPsab{UN$?)BJ;1I=gxiA8j`9f1cT7*}lt2;>uf1`3ctYzq!@lyE5;=bTpk
z9XX|hUVaH?m|~7O5qD|LaUq#%uE}PbZoUa8obH81kxz8~HdI0ZUEOQwiF(
z1b%P{s^yZG9hLx6-^B^3OA1g((xVD}r^64zZ6qjZXHB};p$9>hWpVxybV3xVjS%A?
zL7r$~06dHq7mgTeCTIYs{y3VDp@!#?p@?d5Re{1Chv=TaN~z^+4x*XBv%}ICX|>i~
zi*2=LR+whC-hK;i|G46g%if$l@sXQn>c*xYLIq%zK(Qd^R=`#R3=#lS1;obDRZ#P+S|+T>0OG)4_0^k`o=xJ6f{Kj846=A5JH?u$_rAu^8z
z-9V#_jB>^U|LHr=YPwdbJ2&I3sj(~=Vx|dj0}C$$N+gxO{fHEC8W{M|QC%glTMxT3
zPvEJU-#{9r$kiY*0ur+UUAEY2>6H8s-4H|gZ0w1(LkT4K2=yMfxP&GjcW)OoAT*3tf
z;yn!#C4>!Ap#MPVy$$B?Q6}Ku3Q6d~?-1->2CShCZwM3e&17;o?4b{T2t+x#Er>+S
z;lgCX!y=lb07z1z6Q9^a9U`%UP^_XAuZTsJwCz1v>>@-o61f~w(Tfu49~sYx#>b6~
zCP_@A{~O;3M<2p3j%h?-P1;DuJnC_eZIWV4@W{tN3UZLynj;~rXh$}V@sNycq$A7X
zM=?Hfl9a4uxrQh?4~Ec^2}|S-7s<&`igJ_?Fn(=-T)S6i27fig-3zEYGW<-Ru!Sg7!W@sH
zs5{CsN}wPzRIz{*IYU$0Ly^@Uc-g0_6ltqbL~4+&H11vcGH7{Ps1SAA-c0DMl1ne|F#9fGB;s&=(vT4zKAz=x*fZ??1TL7q%%m9QAatx~Z{
z<9u>d0Eu)7wKPg;SzF4khW0^aHORyMBbgESR=2(dD`KjfAdorK1lYahFa5CF|Nj6+
zRi!enW#ZD50EEngy*0pho4d*W<(5%8$OBppPzDxA2AQ=A-flk#R$}HgQ78oAJQFkk
zBODE1!($eRx~qYGF>R>v0*ZvqtAO6RHN3^qT7*TGllgkrK0Aq#WH_80Y63{a@wzY%
z<@;R}kE+4Gd2o9nVjs0+(8WyZK|Gl@JX9TcKN5B*kJ0N}nr#fbD>kyC0F*!Xh}gyz
zC9!J6D`J5R7+>98@^=$E-RnxFMD7Z*m^bFVEnC-$J1rw~0oqLvE^W@xGOw5gFj|ic
zc|TnqvUG=wV(A_LH%1<^mw^HRA9n_0$ZfQv%i~%c$0sPab=Q5G7n0Zj|Hn5?YEV~V
z!-uNfl0a$3k8KhAXw)j#pPvN}hTamFxy1T%mt$Z6eoz_?jqxZ&C<1W^Q=^vAWmZKf
z3{iAdn#PD^M#(N+6biGLmg&G&km
z2K>!X33i@xqD;L(=fuoRN-0JGWlWG-JmCs<-nR>aXSV<~E^cv4o(FR@UENDYrWP2E
z;T0a4W6f2uF~XvoRWA#}^5R-@IC28MbAty?V8hA5eGJv8`xxu3|5R&A^lY270xg_*
z@_vof;SKH|LA+=_PAS-(+j3mn`f@D?E7F2)DgzPdq*c0i*Rj~0
z6d>?8_z=D}SNK&g4wfRr_o=*XYIrlRKi<=H^;{(sLQl}~^IJFraFaX+_`HeUPHU=9
zkNTHH`XP3bPo1@1w;+3
zwI2kaDR+eiUm|<9$Sl)q%aCdi+^!~OXmv9X76Dg-K%HQ=9{~G0rG6vD!YlfmB(1ihoFMc5a%hyi|FgJrU
zbC6Y4O=yDfg-D&{S%AbEZ-8qMb89Yw1GDuhvgQXDV~12{fy9+*v{x#51r?9SXdK8v
zSOOJ8*n(E(f~F^Ysi=w_v3gE5X$LVMdNPCr;U@_q|ADQS0nhR-T~c=@$V5iCiixFo
zWil;x*cQkqYY9+zd$B2OS9rnKdxN1WsNy3_fFrun7+C@s_mv~$rz1CjP(4yAKq4fm
z;VS4OL&e67DuQ3^I0EXnZf#K*rD1FZ=3Mu7a{EzmOCfgv7i40XFOp|9nAU=zlVDX?
zfd0WROfridXJK5CV}n?WaU&JOqgVy0W$n@%PX<`^wiw8B6>>EyM$s2MSdw4nf;*8f
z0tRjnDLVnP1O*sM`_$N
z7!8?`hY|okaTR~lfWT)Ey+|efk&lZxA;hP5_GpaUhkUxWAB87)dNEIvBZym5Ue!o#
zGct_{GHuiM1~J1JXmw!Q$16e7Gv??J`t>5TVt@L#1f>Bai1B~RsTg}!N`cc)MkyPH
z_Ky~M9Dsu#M-dztId48^EP$dtJw_+BXf$>OQ#m<;4j6-qRhR{+k=m&>BOonR28@Fd
zcrR0HxTyszH)9;xFQ7@94@Ee8B2Nxw|C44`Z#sE!W`>~Zq7(%B6l>U^3E3$AXmtZ8
zaZBkRTXt1XDT6>Em9hbf8G&m8K#6kzAj=3}aRW50LOoN0aL|ZGdG|%8d75p>qY!f|
zA<}KD260yLvDByVz(6>B+=LZ19D2A6<
z+6g5FB3qmKPI4%44N>U}GktTDQ1Zxfq0=tVeMHgI|wJt|tF#e)gJAz7Jw_DX-t6NZdJ
zdlMw4BPv2e$*qS{DE>H@8rpF->X=ySv5DD=k4aUqWO62_U~Bdj%rSFOv2$da0cJ|0
zSO*k}Hc=m&7X@0Je?pd~86x~SF~q`_E0>2FHbyVm54DI5BN6;=~__5uJEH=(()mqR6oo#ugci#0nJEuE2ICy1>ySRg&e
zSC`AICcv_~=o8&)FW!kUz$%P;Au)mnmkN7;0wcPkNR>_0R*MukQp;)A`7{jqBoW4O
zTOqkx2sHfS8660k*kreWBDoj^ymGUlOd@f^A-P6Sf>?7Ddn>x=g>{M|y^3Rim*N&W
zfdgcK7MO!)k630gngeFHAfD;G(S&zq>85FEnrkT)0_&QAw;CnsA+rR$lS^F7vAE0O
zJXTn6E|o4t%8$q)|GhqzvByTL%b^xvYqJr&m?66zyK%mQMj#msSO!|NBhXOAp_BD_
zc3QD%kGE*~Ql)GCC7J2FgswUf0$5;-iB8PMS(w!_2IhE|C?Z9`IdBm4$?39gXpohEiqCuJ~D
zB61{!7m3!LZa7i53pi52tae2gC#6{?rc!$e$0Y@&NK&{R{J}0&JbtWE2-dL?#&$@n
zqHyVUB+G9n+#I%ecS5GdPvM0X9aL7|Q=z&;;#j2dzk=03)`xvgXN}nF0Y=)i*pCg_y7bjAz1NX#*_TaAjfvS{(%2Eb
z*`E#C6fxL@9onUB+AE>h4r4!`RM}SA*`B?a@(k9pecGuw+T{}+v|Zb~EqtjxDn3>l
zJSkQL1JH?uQw3o)D;*`pZPq}Y5`hgwCJ-_cp`uwV+6q7?eO;%{Ba$yY*AHyUM;&x=
z&6tPG+fa1d471l=-Ac!O-tVoLzTKJcoXCkp
zF~iUf%~RtC(xrDouNp7U6P5@5P-?op8|hi`0x#xWiTCW{N|fH3iiqp|az-xD)jH;F
zj$ANa5xCeXBH^VomV0}gySK!wVe1+m+$!mU$0Wlj_Q@Q&+(|vnFr9e|2>+H
zFChczDylLra_8d*Q=#r6Gh7<09v4vVtQ?eLL`hdVW}zwzT?fKY1Bq&QBk0=$p-%!7
z+?D54q8m0HpSPS_p0Vc{Wz&EXqe1TG;Zx=@jh3#)Bu-1jj(!+XG9oD+tkjNR-_E=X
zQn)tl?O-fi;ojrr4)A;=-zdkWc!JleVWNITC5Y=FJiX$-6=ZzVE(rjH2EvmWCfopW
zzP1#)L%CW*PK-0HmKhQky4JIjo@pEceGpy{MvFR*0c?$dw0?>@iAp8W8GqAe6>G%;
zN=3>*|u%d2f-VQ7?i<{_SYCs%Y8#HB!y8U-RC0JSYPc1|9(V|I^-wp
zawaWGU+m%@EcnEoQHmB(k{ds%6+q}D#l5}A{f~JYh72Ev`?lp``+JKvXmYnV)4Pyk
zFZKZ+MU(AjBCnSJX*u?LYo%eoRDx^y>v{$Pbt<*h_bzt*_z?+KAO?~@)gy?eNcf}w
zMg#8^1y75w2XmA^B@IMzHAaBo*^3l^BAgiZg+e411tMpfR3Lx&XZ~s$l8kgZtUPF^
z(l>$0395|oryS9J{}lAqBoJS}f9Dq(>DPbcI2vCIXkPrB%x@X(7xke+B0H>Z0hlFL
zUyaLFjnw}b-QVYe$mOutuE>(1onnEP395KC$p8W1KmkLBGV~aL{~?2bfdw;YSR$Z@
z!U0AE%o(&KqoDu|3l=zVF~GzG2L?b4kVAlikPjtdC}^<9MT~<03?LX&=T4qIef|U*
zROnEmMU5UsnpEjhrcIqbg&GxU0xdM5KFo*}sMa?>R{jVO0Ebu|8VgiaFgBwAEdhLX
zq=_NHk^vJrBHSo|!Ca0l=P-0zG47xQ3=(Qk7*oJusf`^!h8$V)WXhE-U&fqS^JdPS
zml6N~047rbMn?A>ki*Z<5jl+TIT&8JkaAe1E@r@2GICP94tmTJy{~XtQzTDX&idBwL+#2Lj
z)&P$vJOr@#h`%E4!>=rd_|riL{zS4*CjodcA;C9_pl>Jx8vH1VC=%q)tsCr$Nkab;
z;NiZJ2vl&j02+KlfCPKMaET%eJc=Ru1mbI_gJRR5B7}M{$t>rD10V+gl%lPsopAi<
zfCGMPD1pTWaB{A?w35vugRV?6ftkuM4ksfF8-M{V2Vsau>eghlO*h|!6Dq1IbP71`
z-r{bo0`wfKDu>Jqz`Z~T^zF|M-lf&jjVd3@a=wK_wPWstCQx
zk70Yl(!1>Z{D?fT0zEI-N_~sMg!oF-=|TeYlTC*WQ+*=|;T}m2QY}#yg3cO-+!FtPZyxL&Db&8v(G9urrN{
zbV-j&!pT}MXPT{{B&$0Ut{#r`GA_g5s#qu5kS(@mn{UQ>vN<7Jwz@pS;*%|_02S0d
zo@{kU);&$)RC_AvKPn084TC2m
zGLkT*k=_#5CTZH$?Oub#DpGB`2Hde@c@_#w|F-4|fDDlo$Y{_D*4wS;4AE^IVB~n%
zcSW}XcmqsvwdK4F7BF8e1BErxAVr572QjyJ&ztw7zhjcCxD3UENL^*Y)Ytaz&KvPx
z%|jH}-3w_#_+5ier#*D!a>_Et49ZN*c?=}B6)W6Q5@}=E7xq}Z0tkHi4cf4Js3W(g
zH(AZ-k?X+-yGpW4pjQX$)==jI=f8jdJ=>XMB&VaF?FndNQH$fu0}=AjtVyw{-P#C)
zy^ry&Hytxbt9X5-w9|U0tML5F9e9cry5da@@;3O8Z&^1zf%W>$J0gwzpBJx8F
zkm?ep8`=u(l(s*RfGZb@zKiq|ALV7km9#<=daD_0FUd_LGd(aIv(&*L+>(|hHgNh(o~35`D2`H
z81SR|rG{c#GRe%)6w3n`Qhg}<<9lNHB@FcNKTD*|Nk#yx#jMau?rYQ`<5El7STL48
z%o4&=YAC;wBS29s4o$w&E5=z4$rhtlCwXI?t=`XJuAGNOaBM1ShY-pp_
zhjpuJC53EcIh9Q*Zj+m`yTD2~*f}ZDRHjO$DOrKy(2$PcuLbBUaN-)Og*|n(e~Fu9
zWjkBi)^@f6wQX*-cGAww|4z4AV+l@9NSW!CkvcR4fW%tHFw~}uF~5awbRk38%nUGR
ziy5wTwYy#Jc9%`K^=^3M#9Pi-SG<~%$d@ux*YQ&6xfp<^P}0j-l%ChV+@&sN%zHDa
z-q*kX1#p1hA}RqF_%h_(%zh2LrVJ!TGb(k{66^|LpEOv(3&t>H^Lv>KXV}9Z262d|
zOJEU~cq-`BFZfKnVivbpyc@Pmhh02l8rRsrpaGkWYmDFrt60ZB26B)=2;<7gSjb04
za*|01-p|BLiY5r@XO!G29#_}LIz#HEvOHxjcUikcChnCvj2tn0+017~GjnsilmNVj
z4{tt@$Jw>>HZ;BI6|&H{c@@B8YlO^7b7pW#S=JQoxKchR&r&8>(We!f#!}bX)|dkG
zc2a#|Iafz5NqK{MIr7{s)%VuNMmB&i9X2U2v7#NNu%w6Ot6oXlP7ZPK>L~n%UMKr4
zdM?Ve8Le$euNvFpF7xhMJ7^zUmMD>1wW`ZaJ6g|sbtkB|yWc%FZ};2ZLCH0%$*o{?
zLpaLX{-&<)RK&Ps8zTZKY{9$zZ;DsEe@sqFsh;{!j&~f>Epu-wq2^Z9!PA~Tyl9Xd
zGLw)~)SfxD|F}`Ad=!Z*slwcGQR&VqfG_@fAzySfax$49eNj$~G4d)$7v%R
zjnDpiiaEhjNI^^2m(%HO6?363=?}_J9V1c%tz$b!4%uHsVg9|Cx1G`f7dX84+bp1a
zM|?mx5qlcQn!2c$i}sXZ%X5Ud;$>ti##YS
zWS?qt|L#;uvp;owjsnuzVLyK)!~L=guf0ReNd_-+5G~_m9PbE=9{@Km!HJB4xY;ly
z4QMXtxhR$3x9gdSG(ijo;DZVDC<&s!4dlR0k-QqQh#lSJ%B(VCG&^r{h
z5hjWG3m8fOBZv(}iNA@fxS2BwETO%0v%T*yi15%opGu$g7y;7)w@&IlO3D!B865*L
zKC@6AgXph~It|r%Lc)+fE1C}|b2a<`y?uEDC3GY(iM8(YDdGdGD&&a%l8cc#4UEyZ
z4x~d9v%i}gxN+l(@F;+38H)wt2qOfG+yFPgI6{OlfhJ-J&^wc+Q4Ejh4Koor*(j_!
z|FpzQ)5@i-$Ruav0X6EDE`+iyyG9~9K75OdF1)^*|J*V)
zV?%%>$^|n=g{qVujJtLGCTLj#cl?M#9Ei<&i0xUyM${5p*+&>G%C7Xvr&u=9*oJ1?
ztg?JQ)~HDMIskqAl@yE!J}5ucOFJrIOS~bDUJ@deyF`o}ikgeN!&!(e5|)T)iwrQ2
zFsdD9F-a0MMx=v~g2{uhV38uomXDf5z&Z`e+?+Z(k;{R?*2yO#<4;KhXm3@Dl;RA{-zm1hP9HqYz?L0CrIjJVL%dD*_nd96!sX
zWfUW7QVfOUNIXIi00{taB#b$tHGQ!@B2*;)K$rdavjyo;G!jkqa}Ww;jS`&B7}YJ?
zv^RNMN^jy5EP|Fm!6E`gsd#FqyEsoG0U{+jtQ9&8AXOq05H^{yP#J|%q6|x>aH)>7
zQj&@f2<$J6;kX=mC=OD9V8b1}FaeKaI}XYUE+xqhJT>e{M?7pEe36~kbgB9ZojaAV
zj^n9zS%Nw}LMvLk4AP2Bs#Du>AXtMcr83hDaxy0?7tO*db1N6`|9nI&jnYfyCmPkJ
z@8O(s$&Ny8zs9SujdMVZ0Vk*FQY-q>jUZJ!8Xt2@CQOypOB6`UD1g|Sid6~?y17*Q
zlnflyvSz%R#Q?=wHP#T))XM6bNITYMg}quWGD@+|w=z~=B^-Jh?TOYNI1S*CC2E?BYnw@Em^Kr
z+ZnrADarwz&CcxvPfK+WH7OI2ElQ8owt_3#2je4H4Unisj;&=glOSA&h_Cp-T085<
zt>_ZYAy(F?!r}t8AYI#kP1arGHN54BU__0TQ2?mI%Wl!Y(K_49MO_zb+d9bs)w9xv
z5)7w}Kfa(IQDszXq{;+1jY&Nl!kF0&Y*Ue~(YV`PuIO2)3knv{DstJ-$6Ph7+EBrj
z+1~ZtqkT7^l?#z87sD%F4x&%wGE^&KoxPY^pLmJ$|3Nt@J6D~U;1_6?pob}(o2+)wR+M{
z+(->7fe!d8D7OeTc8ZdMI?nkdHpmO!sQ{nNRSW~U03pl(_Hf|@fMGku9Ie~XJQ%*L
zXu1w*&B5)U2N^W5%eSia;XC2V^g=|NIDiWlEcv{_<}y4#qTLs?ye1tAi6{U`q?Ep!
z6pdhrtXyK-h>N~6(z_|*2X0nyz2BlW%>NvxvRDWL{IvoYr!OkNiZI-~s1o16+KcMo
zEqM)B*%DY0Ph~;kG5+HoM&m`Ut&5e63C>b^|1FH?8UVONtsJlt(7h6|i9d>I3Ajkb
zH!NR^)7#-yVU7gC#}SFb6fFT9f!jlymb5B2iZbo%v&ej)YiYik1ih71!!A)~^F4_Pgus!wOEWoKw<~3GBigvTy%t{OYI&Aw
z2~LHg%-5_pd^3?4;Z_744A33s|M^|Bqa7@UpI2hlwuD4Xei|IH6-BgSh8f$s
zIE?s18A55S2MUYQjT-AA*M+v&l|_m*UdokNM{;@tKp8}i5ZZ&FlUur#gT81D3};D{
z83x!@ifO&<87!^F>wSt~iG66<5H*GRm8G$pk_Zj(6yeY%j`LwT`6(!l>SEv(TKpwx
zlTKw_1j*b>ob|vvo6-;(IRVBiK6bN^oEE$8n8|&!T*aCcwv;BdR-b%ql%$5BrS3}x
zun6PKAyQN8SpClPOyWmu+`QiEh>p9-R!1BKOpaXVcRcH*-t9#>=p0z3mm6fR;L7Jv
zPmu6!X%+77*6YD6vc5Lst(f4u|6Qy1{Gr6&=+28kxg+QK8)@Ul)@J#E^l?hWXrQye
z9qPqT;#*Lz6FX@1*___#-%Gc8s*vnR&c-2Xyvb-$90(>k2&oZgf;|XCYBSc;?QU-E
z;iRG_^6p;cYMMjmz8!0@J|MA%O5SMhwBDEj28qP8op9#L^FiR>PUsNV@rwg*;&PI&
z0rFjSQD&K4#xd{UI1+!(UWioel`x629JaC9W%!P4$)0a33P~Nr^FQ<{ltlXRaYB!6BIdT`vvp
z>aOq|S(s-h(VX?!2fo=9jDE6F6pl}pl`}=-uxnr1WbPc2nN6(9n
zo*?(J5h!1qC7<)OmGoA>nZ0I;N`5o#yu4Y*)t@L5+XxH_XOt135@KTVlR!E36w-Nw
zZ0W6Z`G!-2AP+kh2>|^f!K!2b6}ms8WyR1CZehv(AUh4x<;Ri;>od9!`Lm&N=KLAv
zgA^kwS#8BwAtic6Lf#x8iDE7~PrVE^T}Rv}eG9xgV&^n{kE>SNBRv
zPj)zc;RijS@%Tj(X^$@|Yi3DY^Qoobd=By~B7y+uDC
zp4O7Z=R8#^Qec|~`rD0PhvQ#fJ;%M3Mc-|gpeOq*p>yJ1dB{fle&r6tbbD4!JKh~-
zheM2*ar&|E)1ICKr;nVl7kl4On4v?u(@diug=m@5BO&Rtf+Jz3(C^XPD?)FzA)
zM*LYE@jY`QUq|-K_k8MTdBQ(o&HtyH2x$)EH_*2sryAwYH{r~WF~-Lk8j^k2w|&Y8
z{j?u_(!c%QuT9u5vfB6k;SaCeZ;YEv*`6O*;%9#8@>*0A{^pnd-!lHFkj6Y%dw5lT
zDvkc>_kNQC{xJLg@&7NdhMo)>`ciGt0erxR)76>Qw_3qz+8^8guO34GDa6aACm$3Fa&eAV9#wh!_|~EHDToM~7uNf($8gB*~H{PohkzawW@_
zE?>foDRU;xnl^9Z%&BuHN&*1@T;ypm0FOLo@R{i$;D*r~8)F8jC=$Z~0VXjhO^IQk
z$N~cn90)3OEZMROv;=6mD|as4x_0m4&8v4W-@bnT_S9mP<=cgBAx>qx
zG60UW7y}&8$PhAskQsUmP`pGy4J88GYCu3y8BEqgZYx&VHj
z-WebdzTWtH-2c#VgW}qlR0ofYdX)iklbnlN77(^Jb(v=!Yt0b1?c42^S62@&emwc|
z=Fg)~KUpwgh1~sU@D5VGO2*hDM#h-Xvg*nGLFZ-|X@!B-!+i}&rJjNdGT5Ml4?;M|
zZ9t8cns4~zk(LQO9LSMC0#G=IM+izdfM>)Z7@~+1i8Vr0xD7R7Rj5fMnFcSSm;q--
zpcs;LVkw6ZS2wZ70Xj60+M<-i57CZp2
zN{#SU4-Djx*=7fvnkqyAbXJ-Gzkd3V0t(|g*k;XjTe8U~qio)U0pi5Ng-GcTzzt?t
zn%qSN%-A2Pl}YI8sHkeEa9pIu$_GUO^eeQEMlV5{2|9+AfDsIM*cgCVZ`MU(#m7?
z@G??HVQ7HO6A`TWXFE6K^Trhw(C^j;$$HTnRUOw5!M-XbzyQJeI@shIh?@0u!!PTq
zM4X}CdP#3Xdlh)ledoRT=cAwgly8&V)A)&^_S73VP}G=xSY{A-0D3o6IB5M|rZki>
zh{7Dg9E9{o08|5=Vu(gO0~LS)5}Vur=b*p@Ay8ug%nE1T)Eo@3Ono98Aqm$PDgU0h
zOm>wTL039On%s#>0m1`-@RpT>p$RW}1?d{{v{#h^5Q{WN%iSQJlR^0H@Olo+UIMNr
zL5GpAg#8QO_mDUrD@rkoTHGQRX~I5gamj4*vmY7nh8-Qm%wA?ZlVqw_K@oukGnWeB
zMlQ%e&?HU)akLoqcIPUswdFru%L>juWw9u(*iQjz`2JaxiwZr^N7&KD%QXJcp12TK_XhVcaD-
z%W2M+G?FI<;Ef8+&^`g7X#h;ySity@$%qxnQxgH&9JUg%ih$CB1Zx)4zCw|S3?h03
znVyLzvbYYm#ZS-^*H-|rF(V-Ephh@=ElEgAvW-)W=Nu_XOB%Lbf;6PJoL~KfDFDF)
zKnG?bTvW^zz5z1sEeW6}3k8Z9K(Q-<`9Yk4=E%8!ZiI$E4JGw7^)prBlc{e2K*!$V
zO*$%7h|qMJNxSORk*1R;laieo;x!eJDeRS)F%a>?%Joi26J7gn*-ZgyrBBQ)O=#m_&qU;TkPVgz)i(@x!O
z?B%^&_seTT@_of@<}-iw$z?_}o7?Q-F!z_rZiWr`>Z0U1<2ldXQ?pR+tmi)iT9HJ~
zivXI!`zI{G4q8sh#M)8>^ettBiE3G;>>tqBY7S2F)(3=f|M4USva87=#n>{f)
zePmv=R!seu_cmBuu?En2L0D@LmN&hLnl#>Upuu_=TKrtR8T09Q;5Ww$Vl&G+J-TAlU!|+DTCY6@}4(Y
zFpZN!Q;KH8$wMCAIY|L-Gbg1~Clt%MYVK+Vy|;8-jQ`ET)KI^6qsT*U92W=I-a-Z}
zs8a3$Hu`Wsb?V^>#ZCea88-nqfqw9NgUE5hg&pi5$_ZG&7{6lb3o&FPARv$KZ;
z08SWy4xB)Mu3Ua5T+q2nXwLhQ0F+W$O-3gAdTF{B?^fnK4K;XfgL#A&6u4
z-#eZe|Ix%F{h#fm2B<}Z3uR0lA(vE`qr&(RX0S@opvtrSOs%9<&tOK!J)MbsN~drR
zu^<=E9F{>Lo>M*5LM7H(o!|LH9{)7DUo;wo4*VeDOiSP-)!)>MOu^umyc8P!08`;W
z;Jk~^q03CRWJ0NnOvTGfW#N@s&|r;#aUG!$R>f6)MNnwX{I#14eS{vp6UGo9YRu$J
z{?$&Rq>lW+Pa*`ms7qERn-^jbXzV~1#9Twv!5Hko7z`pptR>71A7ZTGsEh%!`JM*6
zWm($X%LyCIjpb0NWw4zk&Fx_uX21@lCCr^=82~^QB*+`-TDF;uFg9D(0RSjEA0(Dv
z(Iu7CIbUWnT`X3U9f)1j@l4S<-}9jcF@9nwiij*0oz&@w(-j>Em1b#<-O|yfRGHmp
zvW42&pe$bE(cxxg=w{S~68~{#CR16I*{S1gt{rWvW)V#%+QkYZZryCA=FnV3h`nBc
znAo1BUlpZbuf0LN+@w_&z)ZSJPwr&Ba3SYCg{<&OC7B4E@FQu=2=#1~3%N)S@IkGJ
z%wj?0u7He2@R&ss7X86vgU*>e&O{0go&f3q&3qw8FpFoXiSGcAM!bOpxrK3VkfvZj
z4q#LnK$yhHN<%UY`^k!iJj`ZvXbCO^6EYL(tOZk!&_{Y!NQxwCc}z>9v862t?_gB95PG0%r!)W*)Vgk+y#fQ?il#*U6BNct!+#UwKAPfWq7h`^M}`GJ&%+9&DC=rqJ&9nog6
zl{~HLjcis)`&1KFBqGFmQxEbmWsK{KprOi#oV(t(=5{A`*
zi)MzP9t7^MeO;&)U2_^6G0J9khMm!!hOxS8C+^VLHSR>fs?pu5(Ftc}8mDlcVf0z6
zr#45cifXHpuJ}o7FPbLtRHuiaE_O2KM6532F;)kPCxd!llTh9m3Dfpa-M`W5JD=9&)9CkTCbT6novln3f0VfSk1#OFXtdv<5cV*
zP*P#g5C}Cb#}W}nJf7BwEZ@2>Mplr}M9^==mHN~f7)h;n1TEoUfXu3-W9%@*!j(T*>^;%-FD9`!gu2n)#rf0ovE<#th^
zbGchvh;3s ^AP-X28o(WPBdYTNF>1(YQo+M$O4Kv^!P-5w?lyCI@*22l1yujrD9dMe?9
zNdNgUYe5_V63EBWfe5d%uKc|&Ep8niBko8f9{PRoj~cRhmJwsT)bFMd0q35A&1abs
z;qgY*C5~*DHV2OZ7xezmZ?*9Cg0h=suUJuv3h_YuneTJ}5y1JQdt_`}UCQrhB)ddy
z0_n;mVdM^-DD`mEi$3L?Fr$r*s>GV#9ObVGhG)zAs-I~90H<&Uz^nkw>Dw^yk>Zbb
z@Xe?YlK^`tEwU@jqVko36Cv%V=KxYLE}{1PD{**^nXcRYDS#7LMF2Q~8W^WH+Y7rs
zGxg2K3zIEg4jZFhYeD>}qT;1vHYU%kB^S`#U+OMF|8ql^R}rrt20`Y+c`>4NvHxMv
z2YQj}=YsJY@3Dy3L1j2!-i7X_W@q4XP3w~G7f-H0T;E4$E*=*~fbEdr3hVT7suN?X
zwce_-p04%vDo}4<>uSeB&?-^ybSM>WwsJ@1I5he^qcZm|RrYRU>}&yyYfNs0K0gNW
zO0w~O9P4e$B{!bV@T(HtNb+^9A53*9%XO0-tZ_sEBxOi)0L(s~9*?w9^AzaDC63yKu>MnTO)$dk%?B+Fv7!mSiZqLGs_CJg#y
zRIDW7sPIClwoJ|Qxs(?Fa5FU1PidLe-JsFgU1>R&N|w&ZISUEu6;sF+5dUOU#Qfn*
z^Y~)aDm4x`flQ5XD7kiNY3DjVjTl&iYDy!tK<~xPmY4r3>OY3FQrfgc&sZxllhFwV)
z5osQ-P7&vVFK%+CogTaKBRX9u!8COiE9ROa8Q(^3j-qgV=5~@EQit8QWjMCVE{XT1
zvc@7Ja%yin?usv+gKvc%AgdVmH<5@Pg^((3o@y93r-R4dHqnzinoL!Hc8;R*K(o|(
z_O5Bqq|dcBd{SVo^wn5z1Xg5e-{4qt#m6PPwLu|F&peUEjQIf~P5&X!b)0utT|)&s
zU5FVt1xbLMbJ>=&fU08oBS1=q_#TpubO>hviUoS1atIeZ)Pp+15g%%4b02%?I4eC##h@cHxT$e{!
z%h?vIFbrx4x&~%4MSx7m4NV5>$4sV5C2pFk6Hl#^UNVoHVxHy9>6{sUxnSaH5WnRc
z%6A#g;kSGHdrNqpB$T!%h+y&zxaTbfdDB$5BIo>?->w_4H#(;tpV>utH5Q#~-pGXm
zMcO5+arS%c(qbj*=5V$y)P?47s$yzV2s?w&f`4WvrRKw7s{a>%<`oKE!V?68vnFan
zlgIZ{#dF|sB0<5hB4{EaAYY=%PvsCY7SeDwRex)Ce0p~s&CV-?xjMU!kor{Xl~fVE
z3nt_aE!MsO_U5JPZSfy^d)HaHkaH
zdG<~CFV}9mwzU1*%UGS(+}tCZ+II?#;{5*2%|=%yg`=tZfmxKh4ph4%+;=N$kv(Ba
z`|3Ek+8h3PV88)h{p7zGoy)|BTvK?w!RB*5xFvCmb-Lq^WOh?N&d;IXx5prZ*}|8^
zZ1(%#GYYPvM*$uFioCT5HU7IjzRK`=>H9u^S$^7pHvfl(zD=;n&HjFD?8mF6+N_mK
zGy279m8bsRyuj_gd7yssYrjcl{r1;J@z(_Od;j=1Xzqvq^^<@4Uw!ba{Zd@0`gcUy
z8$bHPfBoy3`Fj!l+yDMMC_n%R004s_g9i~NRJf2~Lq8hIl(}IBpALu)F=o_=ammGv
zA3=r`Ig(^alP5JQ&=LS;$qa*9#*{geW=)$iJ>Jy0lc&L)J%I)lI+SQpqemMi&>{fo
zOQcVsMwL31YE`ROv1Zk}^{7CCMFZ&I(L;v-6FTtW>(R03&xHjB^cdijYhAk`m9jia
z7vu#UdYi(`h@n6MzHEIaIP8-zhJpqiYf#KM!T%h*0Ui$q3Sb8pkz5w+*im^SXwog+
zGF$*aFhT_a3P3gsfN4pAtwUz`$`C-1AgoE!&Kw;0+uA}2FUBaqcX68;mL~?uxF90|20Xw#p9DBAfgZkNY(wmNsO&?txnbZ#9JVX1za4%Ig-wRskZ_lQ!+{y
zr33P}#GGR;HJ!W+iOC;p8gnEJpi~k#0sjuT^1wIol8_|C9JGuhC#C$sKZMwf4bDFS
z4OGxU2`$t>0tTY;xry*I;|2o+_=|;zG)w5BOEI;ywI%xel1~B|%G6UsNrh}B1;b-u
z(n>XTD8W$?O6{RmY31-#JO=@w%>>}8&4dBpfUM28jPNi;3=8TsfCKjA2nQShaAE*E
z8EY*894tx|SM9bPWI{?@V9^=U}atQg1~AmoXx9!XU-OqziK-HRpt~TYcyJ
zb|ZT;@Qp5jaY_wMGP%_b-&(s}x1ohuH6&nx^vJB!Qc3l$-(VelFxG@Y)-(VaX=FCk
zbX9B^KvFGq&;eUTlYW-GT-~0#Y;671
zt!?tu)`E<;LuGMOg96AQ2fY#))>5a};;>B+Axlwk%DR2_S`YWuYh*ICI6JJc05`6~
z4!`y(v?3*5Ky}e9l$bNt1xsMu#DoM5xYHN#F?G=vz>zoHA>ynKB85yq_0W3vX$;g!
zJWn{#XmMeBFE_AF?Ga>&*^3wnY24<|zH%
zd+=$y4ukQF`#ydSvy?ROW&dY?^h*r;=#?gKK`&QK*&lHB2RDe_CTaJ>9)}1LK&q7}
zRx{$7dNhWrkBQA&t=U_L<_5aCK?hqp&{N4`0~gKp%unH~ptp<`wZxF{gFW=&4}loO
zKOt&2K)C^a(vk<3j35e)bCq0-#w-jNfkjm8K@R}Hhqx3=LmG%nhZLu)9Ms4mYtGi4Mq6795-kTQCX0N|dq`9k2~tF6>Yr|ER|o;vkPM
z#6d>{u#~W5Yh;pa5E`$fGyo+oN+Xa92P0<$P?AkUCX1XClLDi&UF$CbAlZ1>h8dU$
zfOBP;o5%{2OI&eg4F9d;!Wf9RJecHeb*K{;x!NVM*%32$Szrw_XBR5b1nFFLxn&rH~hkY9`w*5Jp@pDf>F|A1ZF-ZA+R^qlb9ahCxCU%a!I}E
z5<8Xj8{h;XpZjEEny{x&eWr6sX!?ylS>w>}Wp8@SVbXPIV6DoN}ml7*eKuO*${I|T@xETtE|_iQPAA<7`Spy@jkLr4&+rzZB+Ri6cP
z=)nZc-;e6AH6xYSmQw0ck2;m35eXgw_4(fUo_D<-jd059n-s_;W<-LuN=?rcu9UIr
z4L^VlApd3LSe3x$Y?Ccn&)Bk5-3+S$CPNk}Bbu26l-R^YHu8~?%+>h_C9GmaqFEi%
z15sX4t!yRGTd(ZeqD^@~3>X2`S|qhIh7nY7iAxTqmgKq+)}}5cAzwy2h{VbmvWSzD
zQGd`nQ)NrejiuzNMzBX5oG;D{VeOD}u$HR6(W#0P5s2>kWvbDZPsLKNv@CZWqTbTC
z{;@HH1`_JL5!aW|OfCSl`?2R1DY^@g9Rz(;-R13I45=Pxt*Z$c=H|+o4l1uxu}Bkg
zhGcpNTc5AqYtMM5b9~gQZ#&BX-+KOrE)Mp||K#V#exlPi=$t6_9tyv1J8+?7gth>q
z^ZywC8I;1#VJUzih|l62%-AG_poiI4Kmb>`Rj*ZZRZ#pHQPHV1J~j9skAt^cRhC9Z
za5w~ekZ3A9@MueO>Rq+=Z;fx9;~h8Dl7oU4C*RBj7>RVK9b;vzfhfy%9RL8vC~6gf
zQ_OtDj+KQLSdU+Y%_(-X3B*?NJ3bPzd8RXFHn?o
zpBJXsN(mxg?HvrFAYUN7H|p+nl24v2r7A})N-^)ryEWs}Aa8Kr(v-^AI{bDJ&3BHV
zJNx|Sc7*ys4t{5ax4q$UDQyqDjPi)Qagw6*qij2G>-MZM-
zRF38SX_>X&68az_U02G*>S9Z{;zOK9Xb1
z#woIRMFSWR;W+EhSVERCPzxU80rd}tyk)5}4yNi)Fn(-B)*=IrCC$ptHAZbA&dxD#
zz#c5(0&^wl2&r`h;O%50cen|>lIxdP?XUE%xE7!edIxrvkRZUzW4Hj;ivLHtaAfgt
z>6^j~?tX%TVoziaBRT%7rPe1)It=qzBcMPpol?))+^f9`;@64x@{B*G;l#clrCLA&F
zZv<@=u|*3Yt8@0qsv_xGTx<}oWv5E<=rX{C+Q#YrsB_>51MbTDVE@C*BF8)qU>J+0
zX=0ICIwb+XE_P%}V!+Q;R4GP$im}}0Y%)O5$m0hY&n0q4yeeb541(@ZZF#IPH0q%5
zoC?>*fC+g`G=S-uX6@IEL^NEoICg+YMk7~j4Km>6Q;;Sj0BkraPlF=w3;ARbE9{)s
z3BdZQ+y-hX_i3JdBYv0zeg+B-O^P`RF_XkCsu1y`poH4?&@9A~*|sF0(yiV2kK3?^
zrK<9z&?ldgh1qydz$Q!sf>QA&(RB#wg3yxp6l$RmhEKZBY0j$~`(pVLP#hWN6Aw-S
zet@w`u^k7A>3p)$<{}%3<}?Jr(x_$_yNGb82xSnnG)?m~um8%chQa`v?8y$3Lkbdy
zfX*xwz&gAy4K>GT)Q3}IlW^3>%z%b10H7fCXLRHaHM`0(hXT%Iq%2M&EMUVdbi=J~
z>0#PNmXgLf3CFU69kH`2K|6y1au$|_%siApmgjs
zj=u3rO^E0MwPjw1CdaExLK7vpQ5-9ABbtLSMz64>q6_=Uex5_4h(bWNFjt7|P+i4K
zkwds{!USle8C@k*MYUC3^;Nk8PlI9vLXF^t>sE0!S9Nt)d9_!4^;dy4ScP?1iM3db
z^;i**P_uJZV3lP|RlP>3DyqXaqN7p=<=tjfCRpl9cfziAlNO(KPB*bsrDDi*2tr=7
zS=Du2*>y8`(Ngv%UgdRO>9t<%^BKSi&vi(2N`urjvd4J+NJ=|Uw+ur{vo%1n
zR{?oq?>>Xpbf+|O%^NpDCS9j$>Lp!yLwz0OTaPkQQ{wkNBR=_reOrQi#s)ruZr`{y
zDFiA@yp?hTrE?cEVuGVOxwkJYj&vEgfo;)rQP*n!fX`f~Zt%!v|AuyxV31t)4_4v_
zcsGPac!aaocZYX!JIGc;7eYvtW-4S=BaRPL@gEcI0JIJ<7PvF}3LphQ6|cxJq>KS<
zc$91_0G=^Jn4&YU=!#HejquO4V*i+laq2=~#0iGCEH;Be@j^$)WiY}QB>;3V#Oo*C
zh-1{GD5=9*O9PE5V=vXqeWhd(zhlCJ0$~2~
zQ6JBN6?u`9Pc`Ep`K!Tta9O#qT)Fpp>K*+6i=E1+I)|rz($L(7u`~w<2_vKcR+(Ce|aW($1_l^4#bPP
zw29VoQklBgi=y>;`qv@U_+NxCEZ1q?-iv%}=-u=MI@nEnG7N&MRrc=ZeWp8oyiG--
zL(`@*E#K`$#~V}ZMW0xqxzUX&gJ&w>@UBzH^1S0|;B4VsyS6^ypiDt{b(~d!4=t
zVr=#Ss;5gF0&|xam4M0BAR5m%?v4&JZe4z*lU7W<{yB}~4WvFV*?Kgd`uM&JY<<|!qjJyO=&QM%f}mg{FLN)Umc-jY
zYNgo6N(kdO