diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..6df3916
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,34 @@
+# Pre-commit hooks configuration
+# See https://pre-commit.com for more information
+
+repos:
+ # General file checks
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v5.0.0
+ hooks:
+ - id: trailing-whitespace
+ - id: end-of-file-fixer
+ - id: check-yaml
+ - id: check-toml
+ - id: check-merge-conflict
+ - id: check-added-large-files
+ args: ['--maxkb=1000']
+ - id: mixed-line-ending
+
+ # Rust formatting
+ - repo: https://github.com/doublify/pre-commit-rust
+ rev: v1.0
+ hooks:
+ - id: fmt
+ name: cargo fmt
+ description: Format Rust code with rustfmt
+ - id: cargo-check
+ name: cargo check
+ description: Check Rust code compilation
+ - id: clippy
+ name: cargo clippy
+ description: Lint Rust code with clippy
+ args: ['--all-features', '--', '-D', 'warnings']
+
+# Optional: run on all files when config changes
+default_install_hook_types: [pre-commit, pre-push]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 01f8213..3a8a3c1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -78,4 +78,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **AUR Package**: Owlen is now available on the Arch User Repository.
### Changed
-- **Build System**: Switched from OpenSSL to rustls for better cross-platform compatibility.
\ No newline at end of file
+- **Build System**: Switched from OpenSSL to rustls for better cross-platform compatibility.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index fe12a3e..d9e4975 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -32,15 +32,14 @@ Unsure where to begin contributing to Owlen? You can start by looking through `g
The process for submitting a pull request is as follows:
1. **Fork the repository** and create your branch from `main`.
-2. **Make your changes.**
-3. **Ensure the code lints and formats correctly.**
- - `cargo fmt --all`
- - `cargo clippy --all -- -D warnings`
+2. **Set up pre-commit hooks** (see [Development Setup](#development-setup) above). This will automatically format and lint your code.
+3. **Make your changes.**
4. **Run the tests.**
- `cargo test --all`
-5. **Add a clear, concise commit message.** We follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
-6. **Push to your fork** and submit a pull request to Owlen's `main` branch.
-7. **Include a clear description** of the problem and solution. Include the relevant issue number if applicable.
+5. **Commit your changes.** The pre-commit hooks will automatically run `cargo fmt`, `cargo check`, and `cargo clippy`. If you need to bypass the hooks (not recommended), use `git commit --no-verify`.
+6. **Add a clear, concise commit message.** We follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
+7. **Push to your fork** and submit a pull request to Owlen's `main` branch.
+8. **Include a clear description** of the problem and solution. Include the relevant issue number if applicable.
## Development Setup
@@ -52,6 +51,46 @@ cd owlen
cargo build
```
+### Pre-commit Hooks
+
+We use [pre-commit](https://pre-commit.com/) to automatically run formatting and linting checks before each commit. This helps maintain code quality and consistency.
+
+**Install pre-commit:**
+
+```sh
+# Arch Linux
+sudo pacman -S pre-commit
+
+# Other Linux/macOS
+pip install pre-commit
+
+# Verify installation
+pre-commit --version
+```
+
+**Setup the hooks:**
+
+```sh
+cd owlen
+pre-commit install
+```
+
+Once installed, the hooks will automatically run on every commit. You can also run them manually:
+
+```sh
+# Run on all files
+pre-commit run --all-files
+
+# Run on staged files only
+pre-commit run
+```
+
+The pre-commit hooks will check:
+- Code formatting (`cargo fmt`)
+- Compilation (`cargo check`)
+- Linting (`cargo clippy --all-features`)
+- General file hygiene (trailing whitespace, EOF newlines, etc.)
+
## Coding Style
- We use `cargo fmt` for automated code formatting. Please run it before committing your changes.
diff --git a/LICENSE b/LICENSE
index 2beb9e1..be3f7b2 100644
--- a/LICENSE
+++ b/LICENSE
@@ -659,4 +659,3 @@ specific requirements.
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
.
-
diff --git a/PKGBUILD b/PKGBUILD
index 127a54a..1c7a975 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -47,4 +47,3 @@ package() {
install -Dm644 "$theme" "$pkgdir/usr/share/$pkgname/themes/$(basename $theme)"
done
}
-
diff --git a/crates/owlen-ollama/Cargo.toml b/crates/owlen-ollama/Cargo.toml
index 3faf672..78a5aaf 100644
--- a/crates/owlen-ollama/Cargo.toml
+++ b/crates/owlen-ollama/Cargo.toml
@@ -31,4 +31,4 @@ uuid = { workspace = true }
async-trait = { workspace = true }
[dev-dependencies]
-tokio-test = { workspace = true }
\ No newline at end of file
+tokio-test = { workspace = true }
diff --git a/crates/owlen-tui/src/chat_app.rs b/crates/owlen-tui/src/chat_app.rs
index 8640d5b..aee741e 100644
--- a/crates/owlen-tui/src/chat_app.rs
+++ b/crates/owlen-tui/src/chat_app.rs
@@ -1171,14 +1171,8 @@ impl ChatApp {
let description =
if self.controller.config().storage.generate_descriptions {
self.status = "Generating description...".to_string();
- match self
- .controller
- .generate_conversation_description()
- .await
- {
- Ok(desc) => Some(desc),
- Err(_) => None,
- }
+ (self.controller.generate_conversation_description().await)
+ .ok()
} else {
None
};
@@ -1780,7 +1774,7 @@ impl ChatApp {
stream,
}) => {
// Step 3: Model loaded, now generating response
- self.status = format!("Model loaded. Generating response... (streaming)");
+ self.status = "Model loaded. Generating response... (streaming)".to_string();
self.spawn_stream(response_id, stream);
match self.controller.mark_stream_placeholder(response_id, "▌") {
diff --git a/crates/owlen-tui/src/ui.rs b/crates/owlen-tui/src/ui.rs
index 103f706..2bfbef2 100644
--- a/crates/owlen-tui/src/ui.rs
+++ b/crates/owlen-tui/src/ui.rs
@@ -1149,7 +1149,14 @@ fn render_help(frame: &mut Frame<'_>, app: &ChatApp) {
frame.render_widget(Clear, area);
let tab_index = app.help_tab_index();
- let tabs = vec!["Navigation", "Editing", "Visual", "Commands", "Sessions", "Browsers"];
+ let tabs = [
+ "Navigation",
+ "Editing",
+ "Visual",
+ "Commands",
+ "Sessions",
+ "Browsers",
+ ];
// Build tab line
let mut tab_spans = Vec::new();
@@ -1284,102 +1291,138 @@ fn render_help(frame: &mut Frame<'_>, app: &ChatApp) {
3 => vec![
// Commands
Line::from(""),
- Line::from(vec![
- Span::styled("COMMAND MODE", Style::default().add_modifier(Modifier::BOLD).fg(theme.info))
- ]),
+ Line::from(vec![Span::styled(
+ "COMMAND MODE",
+ Style::default().add_modifier(Modifier::BOLD).fg(theme.info),
+ )]),
Line::from(" Press ':' to enter command mode, then type one of:"),
Line::from(""),
- Line::from(vec![
- Span::styled("KEYBINDINGS", Style::default().add_modifier(Modifier::BOLD).fg(theme.user_message_role))
- ]),
+ Line::from(vec![Span::styled(
+ "KEYBINDINGS",
+ Style::default()
+ .add_modifier(Modifier::BOLD)
+ .fg(theme.user_message_role),
+ )]),
Line::from(" Enter → execute command"),
Line::from(" Esc → exit command mode"),
Line::from(" Tab → autocomplete suggestion"),
Line::from(" ↑/↓ → navigate suggestions"),
Line::from(" Backspace → delete character"),
Line::from(""),
- Line::from(vec![
- Span::styled("GENERAL", Style::default().add_modifier(Modifier::BOLD).fg(theme.user_message_role))
- ]),
+ Line::from(vec![Span::styled(
+ "GENERAL",
+ Style::default()
+ .add_modifier(Modifier::BOLD)
+ .fg(theme.user_message_role),
+ )]),
Line::from(" :h, :help → show this help"),
Line::from(" :q, :quit → quit application"),
Line::from(" :reload → reload configuration and themes"),
Line::from(""),
- Line::from(vec![
- Span::styled("CONVERSATION", Style::default().add_modifier(Modifier::BOLD).fg(theme.user_message_role))
- ]),
+ Line::from(vec![Span::styled(
+ "CONVERSATION",
+ Style::default()
+ .add_modifier(Modifier::BOLD)
+ .fg(theme.user_message_role),
+ )]),
Line::from(" :n, :new → start new conversation"),
Line::from(" :c, :clear → clear current conversation"),
Line::from(""),
- Line::from(vec![
- Span::styled("MODEL & THEME", Style::default().add_modifier(Modifier::BOLD).fg(theme.user_message_role))
- ]),
+ Line::from(vec![Span::styled(
+ "MODEL & THEME",
+ Style::default()
+ .add_modifier(Modifier::BOLD)
+ .fg(theme.user_message_role),
+ )]),
Line::from(" :m, :model → open model selector"),
Line::from(" :themes → open theme selector"),
Line::from(" :theme → switch to a specific theme"),
Line::from(""),
- Line::from(vec![
- Span::styled("SESSION MANAGEMENT", Style::default().add_modifier(Modifier::BOLD).fg(theme.user_message_role))
- ]),
+ Line::from(vec![Span::styled(
+ "SESSION MANAGEMENT",
+ Style::default()
+ .add_modifier(Modifier::BOLD)
+ .fg(theme.user_message_role),
+ )]),
Line::from(" :save [name] → save current session (with optional name)"),
Line::from(" :w [name] → alias for :save"),
Line::from(" :load, :o, :open → browse and load saved sessions"),
Line::from(" :sessions, :ls → browse saved sessions"),
],
- 4 => vec![ // Sessions
+ 4 => vec![
+ // Sessions
Line::from(""),
- Line::from(vec![
- Span::styled("SESSION MANAGEMENT", Style::default().add_modifier(Modifier::BOLD).fg(theme.info))
- ]),
+ Line::from(vec![Span::styled(
+ "SESSION MANAGEMENT",
+ Style::default().add_modifier(Modifier::BOLD).fg(theme.info),
+ )]),
Line::from(""),
- Line::from(vec![
- Span::styled("SAVING SESSIONS", Style::default().add_modifier(Modifier::BOLD).fg(theme.user_message_role))
- ]),
+ Line::from(vec![Span::styled(
+ "SAVING SESSIONS",
+ Style::default()
+ .add_modifier(Modifier::BOLD)
+ .fg(theme.user_message_role),
+ )]),
Line::from(" :save → save with auto-generated name"),
Line::from(" :save my-session → save with custom name"),
Line::from(" • AI generates description automatically (configurable)"),
Line::from(" • Sessions stored in platform-specific directories"),
Line::from(""),
- Line::from(vec![
- Span::styled("LOADING SESSIONS", Style::default().add_modifier(Modifier::BOLD).fg(theme.user_message_role))
- ]),
+ Line::from(vec![Span::styled(
+ "LOADING SESSIONS",
+ Style::default()
+ .add_modifier(Modifier::BOLD)
+ .fg(theme.user_message_role),
+ )]),
Line::from(" :load, :o, :open → browse and select session"),
Line::from(" :sessions, :ls → browse saved sessions"),
Line::from(""),
- Line::from(vec![
- Span::styled("SESSION BROWSER KEYS", Style::default().add_modifier(Modifier::BOLD).fg(theme.user_message_role))
- ]),
+ Line::from(vec![Span::styled(
+ "SESSION BROWSER KEYS",
+ Style::default()
+ .add_modifier(Modifier::BOLD)
+ .fg(theme.user_message_role),
+ )]),
Line::from(" j/k or ↑/↓ → navigate sessions"),
Line::from(" Enter → load selected session"),
Line::from(" d → delete selected session"),
Line::from(" Esc → close browser"),
Line::from(""),
- Line::from(vec![
- Span::styled("STORAGE LOCATIONS", Style::default().add_modifier(Modifier::BOLD).fg(theme.user_message_role))
- ]),
+ Line::from(vec![Span::styled(
+ "STORAGE LOCATIONS",
+ Style::default()
+ .add_modifier(Modifier::BOLD)
+ .fg(theme.user_message_role),
+ )]),
Line::from(" Linux → ~/.local/share/owlen/sessions"),
Line::from(" Windows → %APPDATA%\\owlen\\sessions"),
Line::from(" macOS → ~/Library/Application Support/owlen/sessions"),
Line::from(""),
- Line::from(vec![
- Span::styled("CONTEXT PRESERVATION", Style::default().add_modifier(Modifier::BOLD).fg(theme.assistant_message_role))
- ]),
+ Line::from(vec![Span::styled(
+ "CONTEXT PRESERVATION",
+ Style::default()
+ .add_modifier(Modifier::BOLD)
+ .fg(theme.assistant_message_role),
+ )]),
Line::from(" • Full conversation history is preserved when saving"),
Line::from(" • All context is restored when loading a session"),
Line::from(" • Continue conversations seamlessly across restarts"),
],
- 5 => vec![ // Browsers
+ 5 => vec![
+ // Browsers
Line::from(""),
- Line::from(vec![
- Span::styled("PROVIDER & MODEL BROWSERS", Style::default().add_modifier(Modifier::BOLD).fg(theme.info))
- ]),
+ Line::from(vec![Span::styled(
+ "PROVIDER & MODEL BROWSERS",
+ Style::default().add_modifier(Modifier::BOLD).fg(theme.info),
+ )]),
Line::from(" Enter → select item"),
Line::from(" Esc → close browser"),
Line::from(" ↑/↓ or j/k → navigate items"),
Line::from(""),
- Line::from(vec![
- Span::styled("THEME BROWSER", Style::default().add_modifier(Modifier::BOLD).fg(theme.info))
- ]),
+ Line::from(vec![Span::styled(
+ "THEME BROWSER",
+ Style::default().add_modifier(Modifier::BOLD).fg(theme.info),
+ )]),
Line::from(" Enter → apply theme"),
Line::from(" Esc / q → close browser"),
Line::from(" ↑/↓ or j/k → navigate themes"),
@@ -1430,7 +1473,12 @@ fn render_help(frame: &mut Frame<'_>, app: &ChatApp) {
.add_modifier(Modifier::BOLD),
),
Span::raw(":Switch "),
- Span::styled("1-6", Style::default().fg(theme.focused_panel_border).add_modifier(Modifier::BOLD)),
+ Span::styled(
+ "1-6",
+ Style::default()
+ .fg(theme.focused_panel_border)
+ .add_modifier(Modifier::BOLD),
+ ),
Span::raw(":Jump "),
Span::styled(
"Esc/q",
diff --git a/docs/migration-guide.md b/docs/migration-guide.md
index f36edde..d30f627 100644
--- a/docs/migration-guide.md
+++ b/docs/migration-guide.md
@@ -4,13 +4,13 @@ This guide documents breaking changes between versions of Owlen and provides ins
As Owlen is currently in its alpha phase (pre-v1.0), breaking changes may occur more frequently. We will do our best to document them here.
----
+---
## Migrating from v0.1.x to v0.2.x (Example)
*This is a template for a future migration. No breaking changes have occurred yet.*
-Version 0.2.0 introduces a new configuration structure for providers.
+Version 0.2.0 introduces a new configuration structure for providers.
### Configuration File Changes
diff --git a/examples/basic_chat.rs b/examples/basic_chat.rs
index e86f1a6..1d8471f 100644
--- a/examples/basic_chat.rs
+++ b/examples/basic_chat.rs
@@ -1,9 +1,9 @@
// This example demonstrates a basic chat interaction without the TUI.
use owlen_core::model::Model;
+use owlen_core::provider::Provider;
use owlen_core::session::Session;
use owlen_ollama::OllamaProvider; // Assuming you have an Ollama provider
-use owlen_core::provider::Provider;
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
diff --git a/examples/custom_provider.rs b/examples/custom_provider.rs
index ffcba34..5bb9075 100644
--- a/examples/custom_provider.rs
+++ b/examples/custom_provider.rs
@@ -16,10 +16,16 @@ impl Provider for MyCustomProvider {
}
async fn chat(&self, session: &Session, model: &Model) -> Result {
- println!("Custom provider received chat request for model: {}", model.name);
+ println!(
+ "Custom provider received chat request for model: {}",
+ model.name
+ );
// In a real implementation, you would send the session data to an API.
let message_count = session.get_messages().len();
- Ok(format!("This is a custom response. You have {} messages in your session.", message_count))
+ Ok(format!(
+ "This is a custom response. You have {} messages in your session.",
+ message_count
+ ))
}
}
diff --git a/examples/session_management.rs b/examples/session_management.rs
index 9419070..20f47a0 100644
--- a/examples/session_management.rs
+++ b/examples/session_management.rs
@@ -23,6 +23,8 @@ fn main() {
println!("\nSession cleared.");
let messages_after_clear = session.get_messages();
- println!("Messages in session after clear: {}", messages_after_clear.len());
+ println!(
+ "Messages in session after clear: {}",
+ messages_after_clear.len()
+ );
}
-
diff --git a/themes/default_light.toml b/themes/default_light.toml
index 15e85a8..47ba764 100644
--- a/themes/default_light.toml
+++ b/themes/default_light.toml
@@ -20,4 +20,4 @@ selection_fg = "black"
cursor = "#d95f02"
placeholder = "gray"
error = "#c0392b"
-info = "green"
\ No newline at end of file
+info = "green"
diff --git a/themes/material-light.toml b/themes/material-light.toml
index 6cbf974..ca7d609 100644
--- a/themes/material-light.toml
+++ b/themes/material-light.toml
@@ -20,4 +20,4 @@ selection_fg = "#212121"
cursor = "#c2185b"
placeholder = "#90a4ae"
error = "#d32f2f"
-info = "#388e3c"
\ No newline at end of file
+info = "#388e3c"