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"