# sued as a library
> [!NOTE]
> This page is about sued as a text editing library. For sued as a text editor,
> see [README.md](README.md).
sued is a stateless vector-oriented command-based text editor written in Rust,
with focus on speed, simplicity, ease of use and staying the hell out of your
way. And now, it's a library!
As of version 0.18.0, the [sued text editor](README.md) can be integrated into
your own programs as a vector-based text editing library.
As of version 0.20.0, sued as a library can also be used to define entirely new
editing commands and use them in your own programs.
And as of version 0.21.0, sued as a library exposes an entire C Foreign Function
Interface, so you can use sued's text editing capabilities in projects that
don't support Rust!
## Usage
### Installing sued as a library
#### From crates.io
```bash
cargo add sued
```
Cargo will automatically build and manage sued as a library for you. You can
update your version of sued as a library by running `cargo update sued`.
#### With sued in your project root
In your Rust project root, clone and build sued as a library:
```bash
git clone https://codeberg.org/AeriaVelocity/sued.git
cd sued
cargo build --release
```
Then, add sued as a dependency in your `Cargo.toml`:
```toml
# ...
[dependencies]
sued = { path = "./sued" }
# ...
```
And build your project:
```bash
cargo build --release
```
You can update your version of sued as a library by running `git pull` in the
cloned sued repository.
#### As a static Rust library
Clone and build sued as a library:
```bash
git clone https://codeberg.org/AeriaVelocity/sued.git
cd sued
cargo build --release
```
Copy `libsued.rlib` to somewhere in your project structure:
```bash
mkdir -p path/to/your/project/libs
cp ./target/release/libsued.rlib path/to/your/project/libs/libsued.rlib
```
Then, create a `build.rs` file in your project root:
```rust
fn main() {
println!("cargo:rustc-link-search={}/libs", env!("CARGO_MANIFEST_DIR"));
println!("cargo:rustc-link-lib=sued"); // you don't need to specify `libsued.rlib`
}
```
And use `extern crate` in your Rust code:
```rust
extern crate sued;
use sued::{ /* ... */ };
fn main() {
// ...
}
```
This method is not recommended for everyday code, and is really only here for
super specific use cases. It's a lot easier to just [use Cargo](#from-cratesio)
to install and manage it.
Updating is difficult, since you'd have to run `git pull`, `cargo build --release`
and copy `libsued.rlib` to your `libs` folder all over again.
#### As a C dynamic library
> [!CAUTION]
> **sued's C FFI is still experimental and immature.** Please remain within the
> confines of Rust if you value functionality and safety (and if you're a
> contributor, your sanity).
> <!-- LINE BREAK!!! -->
> For an example of what the C FFI is currently capable of, see the nested [c-ffi-test](https://codeberg.org/AeriaVelocity/sued/src/branch/main/c-ffi-test) project.
<!-- LINE BREAK THE SEQUEL DEQUEL!!!! -->
> [!NOTE]
> These are instructions for how to do it on Linux with a shared object (`.so`).
> YMMV on other platforms.
Clone and build sued as a library with the `ffi` feature enabled:
```bash
git clone https://codeberg.org/AeriaVelocity/sued.git
cd sued
cargo build --release --features=ffi
```
Copy `libsued.so` to somewhere where your system will look for libraries:
```bash
sudo cp ./target/release/libsued.so /usr/local/lib/libsued.so
```
Write your C program:
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sued.h" // for a definition of sued.h, see `c-ffi-test/include/sued.h`
int main(int argc, char **argv) {
SuedFFI* sued = sued_ffi_instantiate();
if (sued == NULL) {
fprintf(stderr, "failed to instantiate sued ffi\n");
return 1;
}
char** args = strtoargs("show");
ExitStatus s = sued_run_command(args, sued);
// Since we're using `sued_ffi_instantiate`, this is going to show sample
// text, since the C FFI (as of sued v0.21.0) is currently unable to edit
// or replace the text in the file buffer.
printf("%s\n", s.message);
return 0;
}
```
In your GCC command, add these linker flags:
```bash
-L/usr/local/lib -lsued
```
So your command might look like this:
```bash
gcc -o your_program your_source_file.c -L/usr/local/lib -lsued
```
If you're using a Makefile, place the linker flags in your `LDFLAGS` variable
(assuming you use one (I hope you do (if you don't then just use `CFLAGS` I
guess)))
### Using sued in your program
> [!NOTE]
> The source code of the Cake Shop program described in this section is stored
> in [cake-shop-demo](https://codeberg.org/AeriaVelocity/sued/src/branch/main/cake-shop-demo).
Let's say, for the sake of argument, you're making a silly little program
about cake.
There are *two main ways* to utilise sued as a library - the low-level way
through **direct functions** via the `suedfn` module, and the high-level way via
**processing sued commands** through the editor's own `run_sued_command`
function.
Before we delve into those, let's create this preamble for our Cake Shop program:
```rust
use sued::{EditorState, command::CommandRegistry, file_buffer::FileBuffer};
use sued::{run_sued_command, vecify_command};
fn main() {
let cakes = vec![
"chocolate",
"vanilla",
"strawberry"
];
let mut state = EditorState::instantiate();
state.buffer.contents = cakes.iter().map(|s| s.to_string()).collect();
direct_commands(&mut state);
parsed_commands(&mut state);
}
```
#### Manually processing sued commands (`fn direct_commands()`)
sued commands expect to be provided with an `EditorState`. This isn't hard to
create at all, just call `EditorState::instantiate()` to get an `EditorState`
with an empty buffer and default commands.
However, if you want to call a command, it's not as simple as running
`command.call()` - since the command is locked behind an `Arc` (Atomic Reference
Counter) and a `Mutex` (mutually exclusive access), you can't just simply
*call a command directly*.
Instead, you must manually pattern match the existence of the command against
your command registry, and if it exists, unwrap the command and call its action.
```rust
fn direct_commands(state: &mut EditorState) {
let mut cake = String::new();
println!("Welcome to the Direct Cake Shop!");
loop {
println!("Please choose a cake from our list:");
if let Some(command) = CommandRegistry::get_command(&mut state.registry, "show") {
let mut action = command.action.clone();
// The first argument in `args` is expected to be the name of the
// command, and it's ignored by the `show` action.
println!("{}", action.call(vec!["show"], state));
}
std::io::stdin().read_line(&mut cake).unwrap();
cake = cake.trim().to_lowercase();
match cake.as_str() {
"chocolate" | "vanilla" | "strawberry" => {
break;
}
"exit" | "cancel" => {
println!("Order cancelled. Thanks for visiting the Cake Shop!");
return;
}
_ => {
if let Ok(id) = cake.parse::<usize>() {
if id > 0 && id <= state.buffer.contents.len() {
cake = state.buffer.contents[id - 1].to_string();
break;
}
else {
println!("The cake ID {} is not in our system. Please try again.", id);
}
}
else {
println!("We don't stock {} cakes. Please try again.", cake);
}
cake.clear();
}
}
}
println!("You ordered a {} cake.", cake);
let receipt: Vec<String> = vec![
"== Order ==".to_string(),
format!("Cake: {}", cake).to_string(),
"== Total ==".to_string(),
"£19.99".to_string()
];
receipt.iter().for_each(|s| println!("{}", s));
let filename = "receipt-direct.txt";
// Define a new state to save the receipt, so as not to conflict with
// the state defined in `main`, because we need to reuse that state for
// `parsed_commands`.
let mut out_state = EditorState {
buffer: FileBuffer {
contents: receipt,
file_path: Some(filename.to_string())
},
registry: CommandRegistry::instantiate(),
};
if let Some(command) = CommandRegistry::get_command(&mut state.registry, "save") {
let mut action = command.action.clone();
action.call(vec!["save", filename], &mut out_state);
}
println!("Thanks for visiting the Direct Cake Shop!");
}
```
That works, but you may as well be manipulating the text directly. It's useful,
sure, but it's also suuuuuper cumbersome - you have to manually unwrap that
command and call its action yourself. And, sure, you can define a function to
make that more DRY, but it's still Not Great(TM).
You really might as well be using [regex](https://github.com/rust-lang/regex)
or [sd](https://github.com/chmln/sd) or... really just anything that's on the
[Text processing](https://lib.rs/text-processing) list on the lib.rs website.
#### Parsing sued commands (`fn parsed_commands()`)
Now we move onto the real meat and potatoes - processing sued's commands
through the editor's very own `run_sued_function` function! This allows you to
enter sued commands as `Vec<&str>` lines, which will then be parsed and
processed by the editor.
But you probably don't want to write `Vec<Vec<&str>>`s every time you just want
to represent some commands, so there's an easy way to use a simple `Vec<&str>`
instead - through the `vecify_command` function. This will split a `&str` into
a `Vec<&str>` for you, so you can iterate over a `Vec<&str>` and pass each
command through `run_sued_command`.
Here's how you can use `run_sued_command` and `vecify_command` to do things the
sued way:
```rust
fn parsed_commands(state: &mut EditorState) {
let mut cake = String::new();
println!("Welcome to the Parsed Cake Shop!");
loop {
println!("Please choose a cake from our list:");
// Now, we're ready to do things the sued way!
println!("{}", run_sued_command(vecify_command("show"), state));
// See how much more concise that is compared to the `if let Some` code
// from `direct_commands`? This is also more idiomatic, since we only
// need to pull in `EditorState`, `run_sued_command`, and `vecify_command`,
// as opposed to pulling in `CommandRegistry` to look up the command
// ourselves.
std::io::stdin().read_line(&mut cake).unwrap();
cake = cake.trim().to_lowercase();
match cake.as_str() {
"chocolate" | "vanilla" | "strawberry" => {
break;
}
"exit" | "cancel" => {
println!("Order cancelled. Thanks for visiting the Cake Shop!");
return;
}
_ => {
if let Ok(id) = cake.parse::<usize>() {
if id > 0 && id <= state.buffer.contents.len() {
cake = state.buffer.contents[id - 1].to_string();
break;
}
else {
println!("The cake ID {} is not in our system. Please try again.", id);
}
}
else {
println!("We don't stock {} cakes. Please try again.", cake);
}
cake.clear();
}
}
}
println!("You ordered a {} cake.", cake);
let receipt: Vec<String> = vec![
"== Order ==".to_string(),
format!("Cake: {}", cake).to_string(),
"== Total ==".to_string(),
"£19.99".to_string()
];
receipt.iter().for_each(|s| println!("{}", s));
let filename = "receipt-parsed.txt";
let mut out_state = EditorState {
buffer: FileBuffer {
contents: receipt,
file_path: Some(filename.to_string())
},
registry: CommandRegistry::instantiate(),
};
run_sued_command(vecify_command(format!("save {}", filename).as_str()), &mut out_state);
println!("Thanks for visiting the Parsed Cake Shop!");
}
```
This code is much more in-line with why `sued` is even a library in the first
place - **to provide the functions and features of the [sued text editor](README.md)
into other text editors or other programs that work with text.**
This is just a simple example, but you can do so much more with this - wherever
you edit text, you can probably integrate sued somewhere in there.
## Why?
I started a new text editor project, [Astrion](https://codeberg.org/AeriaVelocity/astrion),
and I wanted to see if I could integrate sued into it. Since sued's code is
very lightweight, it wasn't hard to just refactor its text editing capabilities
into a library that I could use in other projects.
So, here you go! sued as a library. Thanks and have fun.
## Legal
sued (including sued as a library) is distributed under the Apache License,
Version 2.0.
See [the Legal section of the main README.md](README.md#legal) for the complete
rundown.