Compare commits

..

7 Commits

12 changed files with 390 additions and 2067 deletions

4
.envrc
View File

@ -1,6 +1,10 @@
use flake || use nix
watch_file flake.nix
PATH_add ./target/debug
PATH_add ./target/release
PATH_add ./scripts
if on_git_branch; then
echo && git status --short --branch &&
echo && git fetch --verbose

2055
Cargo.lock generated

File diff suppressed because it is too large Load Diff

34
build.rs Normal file
View File

@ -0,0 +1,34 @@
/*
* Copyright (C) 2023 - 2024:
* The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* This file is part of the Trixy crate for Trinitrix.
*
* Trixy is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* and the Lesser GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
use trixy::macros::config::trixy::TrixyConfig;
fn main() {
println!("cargo:rerun-if-changed=./dist/*");
println!("cargo:rerun-if-changed=./src/app/command_interface/command_list/api.tri");
let file_tree = TrixyConfig::new("handle_cmd")
.trixy_path("./src/app/command_interface/command_list/api.tri")
.dist_dir_path("./dist")
.generate();
file_tree.materialize().unwrap();
}

120
config/c/plugin.c Normal file
View File

@ -0,0 +1,120 @@
/*
* Copyright (C) 2023 - 2024:
* The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* This file is part of the Trixy crate for Trinitrix.
*
* Trixy is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* and the Lesser GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
#include "../../dist/interface.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define println(args...) \
fprintf(log_file, "\33[32;1m(plugin):\33[0m \33[34;1m"); \
fprintf(log_file, args); \
fprintf(log_file, "\n\33[0m"); \
fflush(log_file);
#define eprintln(args...) \
fprintf(log_file, "\33[32;1m(plugin):\33[0m\33[31;1m "); \
fprintf(log_file, args); \
fprintf(log_file, "\n\33[0m"); \
fflush(log_file);
int is_first_log_file_open = 1;
FILE *get_log_file() {
FILE *log_file;
if (is_first_log_file_open) {
is_first_log_file_open = 0;
log_file = fopen("plugin.txt", "w");
} else {
log_file = fopen("plugin.txt", "wa");
}
if (log_file == NULL) {
printf("Error opening file!\n");
exit(1);
}
return log_file;
}
void handle_error() {
FILE *log_file = get_log_file();
eprintln("GOT an error");
int error_length = last_error_length();
char *error = malloc(error_length);
last_error_message(error, error_length);
eprintln("Encountered error: %s", error);
free(error);
}
void set_normal_mode() {
if (!trinitrix.api.ui.set_mode(Normal))
handle_error();
}
void set_command_mode() {
if (!trinitrix.api.ui.set_mode(Command))
handle_error();
}
void set_insert_mode() {
if (!trinitrix.api.ui.set_mode(Insert))
handle_error();
}
void print_hi() {
if (!trinitrix.api.raw.raise_error("hi!"))
handle_error();
}
void print_warning() {
if (!trinitrix.api.raw.raise_error(
"To exit trinitrix use 'trinitrix.api.exit()' instead!"))
handle_error();
}
int plugin_main() {
FILE *log_file = get_log_file();
println("Hi, setting first keymap!");
if (!trinitrix.api.keymaps.add("ci", "<ESC>", set_normal_mode))
handle_error();
println("Done setting that keymap");
if (!trinitrix.api.keymaps.add("n", ":", set_command_mode))
handle_error();
trinitrix.api.keymaps.add("n", "i", set_insert_mode);
trinitrix.api.keymaps.add("n", "<TAB>", trinitrix.api.ui.cycle_planes);
// a simple test to prove that key chords work
trinitrix.api.keymaps.add("ni", "jj", print_hi);
trinitrix.api.keymaps.add("n", "q", trinitrix.api.exit);
// Help people
trinitrix.api.keymaps.add("n", "<C-c>", print_warning);
// workaround to avoid c de-allocating our nice strings
while (1) {
};
return EXIT_SUCCESS;
}

View File

@ -100,6 +100,8 @@
rust
cargo-edit
cargo-expand
valgrind
];
inherit nativeBuildInputs buildInputs;
};

56
makefile Normal file
View File

@ -0,0 +1,56 @@
# Copyright (C) 2023 - 2024:
# The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
# SPDX-License-Identifier: LGPL-3.0-or-later
#
# This file is part of the Trixy crate for Trinitrix.
#
# Trixy is free software: you can redistribute it and/or modify
# it under the terms of the Lesser GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# and the Lesser GNU General Public License along with this program.
# If not, see <https://www.gnu.org/licenses/>.
BIN_NAME := ./target/plugin.so
BUILD_DIR := ./target/c_build/
# SRC := $(wildcard c/*.c)
SRC := ./config/c/plugin.c
OBJ := $(SRC:.c=.o)
DEP := $(OBJ:.o=.d)
LIBS :=
ALL_CFLAGS := -fPIC -O3 -MMD -Wall -Wextra -Wno-unused-parameter $(CFLAGS) $(CPPFLAGS)
ALL_LDFLAGS := $(addprefix -l,$(LIBS)) -L $(LD_LIBRARY_PATH) $(CFLAGS) $(LDFLAGS)
$(BIN_NAME): $(OBJ)
gcc $(addprefix $(BUILD_DIR),$(notdir $(OBJ))) -shared -o $(addprefix $(BUILD_DIR),$(notdir $(BIN_NAME))) $(ALL_CFLAGS) $(ALL_LDFLAGS)
$(OBJ): $(SRC)
mkdir --parents $(BUILD_DIR)
$(CC) -c $< -o $(addprefix $(BUILD_DIR),$(notdir $(OBJ))) $(ALL_CFLAGS)
.PHONY : clean options
options:
@echo "CC = $(CC)"
@echo "CFLAGS = $(ALL_CFLAGS)"
@echo "LDFLAGS = $(ALL_LDFLAGS)"
@echo "SRC = $(SRC)"
@echo "OBJ = $(OBJ)"
@echo "DEP = $(DEP)"
@echo ""
clean :
rm $(BIN_NAME) $(OBJ) $(DEP)
rm -r $(BUILD_DIR)

40
scripts/valgrind_test.sh Executable file
View File

@ -0,0 +1,40 @@
#! /usr/bin/env sh
# Copyright (C) 2023 - 2024:
# The Trinitrix Project <soispha@vhack.eu, antifallobst@systemausfall.org>
# SPDX-License-Identifier: LGPL-3.0-or-later
#
# This file is part of the Trixy crate for Trinitrix.
#
# Trixy is free software: you can redistribute it and/or modify
# it under the terms of the Lesser GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# and the Lesser GNU General Public License along with this program.
# If not, see <https://www.gnu.org/licenses/>.
cd "$(dirname "$0")" || {
echo "BUG: There is no parent dirname!";
exit 1
}
cd ..
make
cargo build
export TRINITRIX_LOG=info
valgrind --leak-check=full \
--show-leak-kinds=all \
--show-error-list=yes \
--track-origins=yes \
--log-file=./target/valgrind_out.txt \
./target/debug/trinitrix --plugin-path ./target/c_build/plugin.so
cat ./target/valgrind_out.txt

View File

@ -1,22 +1,29 @@
use std::{ffi::c_int, path::Path};
use std::{
ffi::c_int,
path::PathBuf,
thread::{self},
};
use anyhow::{Context, Result};
use cli_log::info;
use libloading::{Library, Symbol};
pub fn load(plugin: &Path) -> Result<()> {
pub fn load(plugin: PathBuf) -> Result<()> {
info!("Loading a plugin from '{}'", plugin.display());
unsafe {
let lib = Library::new(plugin).context("Failed to load plugin")?;
let func: Symbol<unsafe fn() -> c_int> = lib
.get(b"plugin_main")
.context("Plugin does not have a 'plugin_main' symbol")?;
let _handle = thread::spawn(move || -> Result<()> {
unsafe {
let lib = Library::new(plugin).context("Failed to load plugin")?;
let func: Symbol<unsafe fn() -> c_int> = lib
.get(b"plugin_main")
.context("Plugin does not have a 'plugin_main' symbol")?;
info!("Starting plugin");
let out = func();
info!("Plugin finished with: {}", out);
}
info!("Starting plugin");
let out = func();
info!("Plugin finished with: {}", out);
}
Ok(())
});
Ok(())
}

View File

@ -179,7 +179,6 @@ pub async fn handle<U: TrinitrixUi>(
}
Raw::send_input_unprocessed { input } => {
let key = Key::from_str(input.as_str())?;
// let cross_input: Event = key.try_into()?;
match app.status.state() {
State::Insert | State::Command => {

View File

@ -6,7 +6,7 @@ pub mod status;
use std::{collections::HashMap, path::PathBuf, sync::OnceLock};
use anyhow::{Context, Result};
use cli_log::warn;
use cli_log::{debug, warn};
use directories::ProjectDirs;
use keymaps::trie::Node;
use tokio::sync::mpsc::{self, Sender};
@ -93,7 +93,7 @@ impl<U: TrinitrixUi> App<U> {
}
if let Some(plugin) = plugin_path {
config::shared_objects::load(&plugin)
config::shared_objects::load(plugin.clone())
.with_context(|| format!("Failed to load a pluging at '{}'", plugin.display()))?;
}

View File

@ -2,6 +2,7 @@ mod app;
mod cli;
mod ui;
use anyhow::Context;
use clap::Parser;
use crate::{
@ -18,13 +19,13 @@ async fn main() -> anyhow::Result<()> {
match command {
Command::Start {} => {
todo!("The full ui is not yet finished");
let mut app = app::App::new(Repl::new())?;
let mut app = app::App::new(Repl::new()?)?;
// NOTE(@soispha): The `None` here is temporary <2024-05-03>
app.run(None, args.plugin_path).await?;
}
Command::Repl {} => {
let mut app = app::App::new(Repl::new())?;
let mut app = app::App::new(Repl::new().context("Failed to setup repl")?)?;
// NOTE(@soispha): The `None` here is temporary <2024-05-03>
app.run(None, args.plugin_path).await?;

View File

@ -1,51 +1,128 @@
use anyhow::Result;
use cli_log::info;
use std::io::{self, Stdout, Write};
use anyhow::{Context, Result};
use cli_log::{debug, info};
use crossterm::{
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnableLineWrap},
};
use keymaps::key_repr::{Key, KeyValue};
use crate::app::status::Status;
use crate::app::status::{State, Status};
use super::ui_trait::TrinitrixUi;
pub struct Repl {
current_written_buffer: String,
current_input: String,
enter_new_line: bool,
stdout: Stdout,
}
impl Repl {
pub fn new() -> Self {
println!("Welcome to the Trinitrix repl! You can now enter trinitry commands.");
print!("{}", Self::prompt(&Status::new()));
pub fn new() -> Result<Self> {
enable_raw_mode().context("Failed to enable raw mode")?;
let mut stdout = io::stdout();
Repl {
write!(
stdout,
"Welcome to the Trinitrix repl! You can now enter trinitry commands."
)?;
write!(stdout, "\n\r{}", Self::prompt(&Status::new()))?;
stdout.flush()?;
Ok(Repl {
current_written_buffer: String::new(),
current_input: String::new(),
enter_new_line: false,
}
stdout,
})
}
pub fn prompt(status: &Status) -> String {
format!("{}> ", status.state())
}
}
impl Drop for Repl {
fn drop(&mut self) {
disable_raw_mode().expect("Must work in drop");
execute!(io::stdout(), EnableLineWrap).expect("Must also work");
writeln!(self.stdout, "\n\rBye!").expect("Must also work");
}
}
impl TrinitrixUi for Repl {
async fn update(&mut self, status: &Status) -> Result<()> {
if self.enter_new_line {
info!("Got '{}' from user in repl.", self.current_input);
info!("Got '{}' from user in repl.", self.current_written_buffer);
if status.state() == &State::Command {
write!(
self.stdout,
"\n\rEvaluating: '{}' ..",
self.current_written_buffer
)?;
} else {
write!(
self.stdout,
"\n\rCan't insert anything in the repl besides in command mode!",
)?;
}
self.enter_new_line = false;
self.current_input = String::new();
self.current_written_buffer.clear();
print!("{}", Self::prompt(status));
write!(self.stdout, "\n\r{}", Self::prompt(status))?;
} else {
write!(
self.stdout,
"\r{}{}{}",
Self::prompt(status),
self.current_written_buffer,
self.current_input
)?;
self.current_written_buffer += self.current_input.as_str();
self.current_input.clear();
}
self.stdout.flush()?;
Ok(())
}
fn input(&mut self, input: Key) {
debug!("Input received at repl: {}", input);
if let Some(value) = input.value() {
if let KeyValue::Char(ch) = value {
self.current_input.push(*ch);
} else if let KeyValue::Enter = value {
self.enter_new_line = true;
match value {
KeyValue::Backspace => {
self.current_written_buffer.pop();
}
KeyValue::Enter => self.enter_new_line = true,
KeyValue::Left => todo!(),
KeyValue::Right => todo!(),
KeyValue::Up => todo!(),
KeyValue::Down => todo!(),
KeyValue::Home => todo!(),
KeyValue::End => todo!(),
KeyValue::PageUp => todo!(),
KeyValue::PageDown => todo!(),
KeyValue::Tab => todo!(),
KeyValue::BackTab => todo!(),
KeyValue::Delete => todo!(),
KeyValue::Insert => todo!(),
KeyValue::F(_) => todo!(),
KeyValue::Char(ch) => self.current_input.push(*ch),
KeyValue::Null => todo!(),
KeyValue::Esc => todo!(),
KeyValue::CapsLock => todo!(),
KeyValue::ScrollLock => todo!(),
KeyValue::NumLock => todo!(),
KeyValue::PrintScreen => todo!(),
KeyValue::Pause => todo!(),
KeyValue::Menu => todo!(),
KeyValue::KeypadBegin => todo!(),
}
} else {
info!("User wrote: '{}'", input.to_string_repr());