Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[env]
# Live tests share one account's API rate limit, so run tests one at a time.
# An explicitly set RUST_TEST_THREADS still takes precedence.
RUST_TEST_THREADS = "1"
18 changes: 16 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,39 @@

First off, thank you for considering contributing to vimstoat!

## You Don't Have to Write Code

Opening an issue is plenty of help on its own. Bug reports, feature ideas, questions, and "this felt weird" feedback are all welcome, and you don't need to follow up with a pull request.

## Pull Request Process

**CRITICAL:** All Pull Requests MUST be made against the `staging` branch.

Do **NOT** open Pull Requests directly against the `main` branch. The `main` branch is reserved for production releases only.

### Steps to Contribute:

1. Fork the repository.
2. Create a new branch off `staging` for your feature or bugfix.
3. Make your changes and commit them using standard naming practices (e.g. `fix: the thing`).
4. Push your branch to your fork.
5. Open a Pull Request targeting the `staging` branch of the upstream repository using the same naming format for your PR title.

### Adding a Feature

If your change is a `feat`, please also:

- **Update the docs.** Document the new feature or keybind in `docs/vimstoat/src/` (e.g. `features.md`, `keybinds.md`).
- **Add unit tests** where relevant, covering the new behavior.

## Commit & PR Naming Conventions

Standard naming practices (Conventional Commits) are advised for both commit messages and PR titles:

Format: `<type>: <description>`

Common types:

- `fix: <description>` — Bug fixes (e.g. `fix: the thing`)
- `feat: <description>` — New features
- `docs: <description>` — Documentation changes
Expand All @@ -30,7 +43,8 @@ Common types:
- `chore: <description>` — Routine tasks, dependencies, or maintenance

**Example:**
* `fix: the thing`
* `feat: add support for custom keybindings`

- `fix: the thing`
- `feat: add support for custom keybindings`

Thanks for helping make this project better!
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "vimstoat"
version = "0.1.0"
version = "0.1.4"
edition = "2024"

[dependencies]
Expand Down Expand Up @@ -34,6 +34,7 @@ serde = { version = "1", features = ["derive"] }
tokio = { version = "1.52.3", features = ["full"] }
serde_json = "1.0.150"
ulid = "3.0.0"
toml = "1.1.8"

[dev-dependencies]
cargo-husky = { version = "1.5.0", default-features = false, features = [
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,13 @@ VimStoat connects to `https://api.stoat.chat` by default. For self-hosted instan
```toml
[instance]
url = "https://api.your-instance.com"
ws_url = "wss://events.your-instance.com"
```

A default config file is generated on first run. Missing keys fall back to their defaults. If the file is invalid, VimStoat shows a warning and uses the defaults without modifying the file.

The `API_BASE_URL` and `WS_BASE_URL` environment variables are no longer supported; set these values in the config file instead.

## License

[GPL-3.0](LICENSE)
1 change: 1 addition & 0 deletions docs/vimstoat/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@

- [Features](./features.md)
- [Keybinds](./keybinds.md)
- [Configuration](./config.md)
53 changes: 53 additions & 0 deletions docs/vimstoat/src/config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Configuration

VimStoat reads its settings from a TOML file in your OS config directory:

| OS | Path |
| ------- | ------------------------------------------------------ |
| Linux | `~/.config/vimstoat/config.toml` (or `$XDG_CONFIG_HOME/vimstoat/config.toml`) |
| macOS | `~/Library/Application Support/vimstoat/config.toml` |
| Windows | `%APPDATA%\vimstoat\config.toml` |

Anything described as **Planned** is not implemented yet.

## First Run
If the file doesn't exist, VimStoat creates it with the default settings, creating the `vimstoat` directory if needed. It never overwrites an existing file.

## Default Config

```toml
# vimstoat configuration

[instance]
# REST API base URL
url = "https://api.stoat.chat"
# Websocket events URL
ws_url = "wss://events.stoat.chat"
```

## Options

### `[instance]`
The Stoat server to connect to. Change these to use a self-hosted instance.

| Key | Default | Description |
| -------- | ------------------------- | --------------------------------------------------- |
| `url` | `https://api.stoat.chat` | REST API base URL. Must start with `http://` or `https://`. |
| `ws_url` | `wss://events.stoat.chat` | WebSocket events URL. Must start with `ws://` or `wss://`. |

A trailing `/` on either URL is ignored.

The `API_BASE_URL` and `WS_BASE_URL` environment variables are no longer supported. Set these values in the config file instead.

## Missing and Invalid Settings
- **Missing keys**: Any key you leave out uses its default, so a config containing only `url` is valid.
- **Invalid file**: If the file can't be read, isn't valid TOML, has a value of the wrong type, contains an unknown key (for example a typo like `urll`), or has a URL with the wrong scheme, VimStoat:
- shows a **Config Warning** pop-up explaining what is wrong and where, which any key dismisses,
- writes the warning to the log file,
- uses **all** default settings for that run,
- leaves your file untouched so you can fix it.

## Planned
These options are not implemented yet:
- **Silent typing**: Keep typing without telling other people you are typing.
- **Notification message content**: Choose whether desktop notifications show the message text.
3 changes: 1 addition & 2 deletions docs/vimstoat/src/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,13 @@ What you see in the UI is the live state built from the server and the real-time
## Vim-Native Interface
- **Mode-Specific UI Themes**: Border colors dynamically shift based on the current mode (Blue for UI/Normal, Yellow for Insert, Green for Command).
- **Cursor Shaping**: Hardware cursor shape changes depending on mode (blinking bar in Insert and Command mode, block otherwise).
- **Input Motions**: Features vim-like text composition motions like `i`, `I`, `a`, `A`, `o`, `O`, and `dd` (delete line), with an in-memory yank buffer. Pasting from the yank buffer is **Planned**.
- **Input Motions**: Features vim-like text composition motions like `i`, `I`, `a`, `A`, `o`, `O`, and `dd` (delete line), `yy` (yank line) and `p` (paste), with an in-memory yank buffer.
- **Multi-line messages**: `Shift+Enter` or `Alt+Enter` inserts a newline; `Enter` sends.
- **Screen Navigation**: `:q` goes back one screen at a time, `q` goes back from a conversation, and `:qa` quits globally. See [Keybinds](./keybinds.md).

## Planned
- Fuzzy finder (`fs`, `fc`, `ff`, `fm`)
- Search (`/`)
- Visual mode (`v`)
- Pasting the yank buffer
- Browsing server channels
- Broadcasting your own typing indicator
14 changes: 9 additions & 5 deletions docs/vimstoat/src/keybinds.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ Keys marked **(Planned)** are not implemented yet.

| Key | Action |
| ---- | -------------------------------------------------------------------------------------------- |
| `dd` | Delete the current line of the message. The line is saved to the yank buffer (`p` to paste it back is **Planned**). |
| `dd` | Delete the current line of the message. The line is saved to the yank buffer. |
| `yy` | Copy the current line of the message to the yank buffer. |
| `p` | Paste the yank buffer as a new line below the current line. |

## Insert Mode

Expand All @@ -61,10 +63,12 @@ Keys marked **(Planned)** are not implemented yet.

Press `:` to enter Command mode, type a command, then press `Enter`. `Esc` cancels and `Backspace` edits the command.

| Command | Action |
| ------------------------------------ | ----------------------------------------------------------- |
| `:q`, `:quit`, `:q!` | Go back to the previous screen, or quit if at the top level |
| `:qa`, `:qall`, `:qa!`, `:qall!` | Quit the application immediately |
| Command | Action |
| ---------------------------------------------------------------------- | ----------------------------------------------------------- |
| `:q`, `:quit`, `:q!`, `:wq`, `:wq!` | Go back to the previous screen, or quit if at the top level |
| `:qa`, `:qall`, `:qa!`, `:qall!`, `:wqa`, `:wqall`, `:wqa!`, `:wqall!` | Quit the application immediately |

`:wq` and `:wqa` are aliases for `:q` and `:qa`, so Vim muscle memory works. There is nothing to write, so they behave exactly like their quit counterparts.

Unknown commands are ignored (a warning is written to the log file).

Expand Down
2 changes: 2 additions & 0 deletions src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pub enum Action {
OpenNewLineBelow,
OpenNewLineAbove,
DeleteLine,
YankLine,
Paste,
Enter,
CursorLeft,
CursorRight,
Expand Down
20 changes: 3 additions & 17 deletions src/api/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,21 +50,7 @@ impl Auth {
#[cfg(test)]
mod tests {
use super::*;

/// Reads the user's real session token from the keyring (read-only).
/// Panics if the keyring or a stored token is unavailable.
async fn real_token() -> String {
let auth = Auth::new().expect("keyring must be available to run auth tests");

let token = auth
.token_entry
.get_secret()
.await
.expect("a vimstoat session token must be stored in the keyring");

assert!(!token.is_empty(), "stored keyring token is empty");
token
}
use crate::test_helpers::keyring_token;

/// Builds an `Auth` backed by a test-specific keyring entry.
/// Never touches the real vimstoat entry.
Expand All @@ -76,7 +62,7 @@ mod tests {

#[tokio::test]
async fn test_validate_token_accepts_real_token() {
let token = real_token().await;
let token = keyring_token("auth").await;
let auth = Auth::new().unwrap();

let client = auth
Expand Down Expand Up @@ -114,7 +100,7 @@ mod tests {

#[tokio::test]
async fn test_real_token_works_with_authenticated_endpoint() {
let token = real_token().await;
let token = keyring_token("auth").await;

let client = ApiClient::new(token, None);
let me = client
Expand Down
Loading
Loading