Compare commits
42 commits
sqlite-per
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 10214f3369 | |||
| 2ca6190d97 | |||
| 67e77c8880 | |||
| b70d7548da | |||
| 732d462775 | |||
| 40de073799 | |||
| 8b928184e8 | |||
| ca0f0b6c31 | |||
| 74fbf386b2 | |||
| a17630aeaa | |||
| 496cdde18d | |||
| 6157ca5088 | |||
| 30c344031a | |||
| 4cf6a15577 | |||
| b207e91c25 | |||
| 676c92752d | |||
| cc436bbb3a | |||
| 56896a861e | |||
| 03b91ec1cd | |||
| cab37cb633 | |||
| 967293db37 | |||
| 972e4938aa | |||
| b64f56fce5 | |||
| b4c4a89625 | |||
| 9435fbece6 | |||
| 315db43010 | |||
| 24c8c92070 | |||
| bf9a9d640b | |||
| 8040b82ff1 | |||
| 17185ea536 | |||
| 900a686d0d | |||
| 2fa1bec421 | |||
| e750f81b11 | |||
| 866176dab6 | |||
| bf11e055b6 | |||
| 6c884f3077 | |||
| d29e3e6651 | |||
| fbc64de607 | |||
| 816d8f86a3 | |||
| 25d2cc7c98 | |||
| f45e66f572 | |||
| bd43fe060b |
56 changed files with 1361 additions and 951 deletions
75
.github/workflows/build.yml
vendored
Normal file
75
.github/workflows/build.yml
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
# What software is installed by default:
|
||||||
|
# https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
|
||||||
|
|
||||||
|
name: build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- ubuntu-22.04
|
||||||
|
- windows-latest
|
||||||
|
- macos-latest
|
||||||
|
- macos-13
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- name: Check out repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up rust
|
||||||
|
run: rustup update
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cargo build --release
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: cargo test --release
|
||||||
|
|
||||||
|
- name: Record target triple
|
||||||
|
run: rustc -vV | awk '/^host/ { print $2 }' > target/release/host
|
||||||
|
|
||||||
|
- name: Upload
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: cove-${{ matrix.os }}
|
||||||
|
path: |
|
||||||
|
target/release/cove
|
||||||
|
target/release/cove.exe
|
||||||
|
target/release/host
|
||||||
|
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
|
needs:
|
||||||
|
- build
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Download artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
|
||||||
|
- name: Zip artifacts
|
||||||
|
run: |
|
||||||
|
chmod +x cove-ubuntu-22.04/cove
|
||||||
|
chmod +x cove-windows-latest/cove.exe
|
||||||
|
chmod +x cove-macos-latest/cove
|
||||||
|
chmod +x cove-macos-13/cove
|
||||||
|
zip -jr "cove-$(cat cove-ubuntu-22.04/host).zip" cove-ubuntu-22.04/cove
|
||||||
|
zip -jr "cove-$(cat cove-windows-latest/host).zip" cove-windows-latest/cove.exe
|
||||||
|
zip -jr "cove-$(cat cove-macos-latest/host).zip" cove-macos-latest/cove
|
||||||
|
zip -jr "cove-$(cat cove-macos-13/host).zip" cove-macos-13/cove
|
||||||
|
|
||||||
|
- name: Create new release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
body: Automated release, see [CHANGELOG.md](CHANGELOG.md) for more details.
|
||||||
|
files: "*.zip"
|
||||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
|
@ -2,7 +2,7 @@
|
||||||
"files.insertFinalNewline": true,
|
"files.insertFinalNewline": true,
|
||||||
"rust-analyzer.cargo.features": "all",
|
"rust-analyzer.cargo.features": "all",
|
||||||
"rust-analyzer.imports.granularity.enforce": true,
|
"rust-analyzer.imports.granularity.enforce": true,
|
||||||
"rust-analyzer.imports.granularity.group": "module",
|
"rust-analyzer.imports.granularity.group": "crate",
|
||||||
"rust-analyzer.imports.group.enable": true,
|
"rust-analyzer.imports.group.enable": true,
|
||||||
"evenBetterToml.formatter.columnWidth": 100,
|
"evenBetterToml.formatter.columnWidth": 100,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
91
CHANGELOG.md
91
CHANGELOG.md
|
|
@ -4,32 +4,83 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
Procedure when bumping the version number:
|
Procedure when bumping the version number:
|
||||||
1. Update dependencies and flake in a separate commit
|
|
||||||
|
1. Update dependencies in a separate commit
|
||||||
2. Set version number in `Cargo.toml`
|
2. Set version number in `Cargo.toml`
|
||||||
3. Add new section in this changelog
|
3. Add new section in this changelog
|
||||||
4. Run `cargo run help-config > CONFIG.md`
|
4. Run `cargo run help-config > CONFIG.md`
|
||||||
5. Commit with message `Bump version to X.Y.Z`
|
5. Commit with message `Bump version to X.Y.Z`
|
||||||
6. Create tag named `vX.Y.Z`
|
6. Create tag named `vX.Y.Z`
|
||||||
7. Fast-forward branch `latest`
|
7. Push `master` and the new tag
|
||||||
8. Push `master`, `latest` and the new tag
|
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
### Updated
|
### Changed
|
||||||
- Documentation for `time_zone` config option
|
|
||||||
|
- Display emoji user id hashes in the nick list
|
||||||
|
- Compile linux binary with older glibc version
|
||||||
|
|
||||||
|
## v0.9.3 - 2025-05-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Key bindings for emoji-based user id hashing
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- `keys.rooms.action.connect_autojoin` connecting to non-autojoin rooms
|
||||||
|
|
||||||
|
## v0.9.2 - 2025-03-14
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `bell_on_mention` config option
|
||||||
|
|
||||||
|
## v0.9.1 - 2025-03-01
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Rendering glitches with unicode-based width estimation
|
||||||
|
|
||||||
|
## v0.9.0 - 2025-02-23
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Unicode-based grapheme width estimation method
|
||||||
|
- `width_estimation_method` config option
|
||||||
|
- `--width-estimation-method` option
|
||||||
|
- Room links are now included in the `I` message links list
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Updated documentation for `time_zone` config option
|
||||||
|
- When connecting to a room using `n` in the room list, the cursor now moves to that room
|
||||||
|
- Updated list of emoji names
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Special handling of &rl2dev
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Nick color in rare edge cases
|
||||||
|
- Message link list rendering bug
|
||||||
|
|
||||||
## v0.8.3 - 2024-05-20
|
## v0.8.3 - 2024-05-20
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Updated list of emoji names
|
- Updated list of emoji names
|
||||||
|
|
||||||
## v0.8.2 - 2024-04-25
|
## v0.8.2 - 2024-04-25
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Renamed `json-stream` export format to `json-lines` (see <https://jsonlines.org/>)
|
- Renamed `json-stream` export format to `json-lines` (see <https://jsonlines.org/>)
|
||||||
- Changed `json-lines` file extension from `.json` to `.jsonl`
|
- Changed `json-lines` file extension from `.json` to `.jsonl`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Crash when window is too small while empty message editor is visible
|
- Crash when window is too small while empty message editor is visible
|
||||||
- Mistakes in output and docs
|
- Mistakes in output and docs
|
||||||
- Cove not cleaning up terminal state properly
|
- Cove not cleaning up terminal state properly
|
||||||
|
|
@ -37,16 +88,19 @@ Procedure when bumping the version number:
|
||||||
## v0.8.1 - 2024-01-11
|
## v0.8.1 - 2024-01-11
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Support for setting window title
|
- Support for setting window title
|
||||||
- More information to room list heading
|
- More information to room list heading
|
||||||
- Key bindings for live caesar cipher de- and encoding
|
- Key bindings for live caesar cipher de- and encoding
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Key binding to open present page
|
- Key binding to open present page
|
||||||
|
|
||||||
## v0.8.0 - 2024-01-04
|
## v0.8.0 - 2024-01-04
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Support for multiple euph server domains
|
- Support for multiple euph server domains
|
||||||
- Support for `TZ` environment variable
|
- Support for `TZ` environment variable
|
||||||
- `time_zone` config option
|
- `time_zone` config option
|
||||||
|
|
@ -56,6 +110,7 @@ Procedure when bumping the version number:
|
||||||
- Welcome info box next to room list
|
- Welcome info box next to room list
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- The default euph domain is now https://euphoria.leet.nu/ everywhere
|
- The default euph domain is now https://euphoria.leet.nu/ everywhere
|
||||||
- The config file format was changed to support multiple euph servers with different domains.
|
- The config file format was changed to support multiple euph servers with different domains.
|
||||||
Options previously located at `euph.rooms.*` should be reviewed and moved to `euph.servers."euphoria.leet.nu".rooms.*`.
|
Options previously located at `euph.rooms.*` should be reviewed and moved to `euph.servers."euphoria.leet.nu".rooms.*`.
|
||||||
|
|
@ -64,17 +119,20 @@ Procedure when bumping the version number:
|
||||||
- Reduced connection timeout from 30 seconds to 10 seconds
|
- Reduced connection timeout from 30 seconds to 10 seconds
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Room deletion popup accepting any room name
|
- Room deletion popup accepting any room name
|
||||||
- Duplicated key presses on Windows
|
- Duplicated key presses on Windows
|
||||||
|
|
||||||
## v0.7.1 - 2023-08-31
|
## v0.7.1 - 2023-08-31
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Updated dependencies
|
- Updated dependencies
|
||||||
|
|
||||||
## v0.7.0 - 2023-05-14
|
## v0.7.0 - 2023-05-14
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Auto-generated config documentation
|
- Auto-generated config documentation
|
||||||
- in [CONFIG.md](CONFIG.md)
|
- in [CONFIG.md](CONFIG.md)
|
||||||
- via `help-config` CLI command
|
- via `help-config` CLI command
|
||||||
|
|
@ -82,6 +140,7 @@ Procedure when bumping the version number:
|
||||||
- `measure_widths` config option
|
- `measure_widths` config option
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Overhauled widget system and extracted generic widgets to [toss](https://github.com/Garmelon/toss)
|
- Overhauled widget system and extracted generic widgets to [toss](https://github.com/Garmelon/toss)
|
||||||
- Overhauled config system to support auto-generating documentation
|
- Overhauled config system to support auto-generating documentation
|
||||||
- Overhauled key binding system to make key bindings configurable
|
- Overhauled key binding system to make key bindings configurable
|
||||||
|
|
@ -95,15 +154,18 @@ Procedure when bumping the version number:
|
||||||
## v0.6.1 - 2023-04-10
|
## v0.6.1 - 2023-04-10
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Improved JSON export performance
|
- Improved JSON export performance
|
||||||
- Always show rooms from config file in room list
|
- Always show rooms from config file in room list
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Rooms reconnecting instead of showing error popups
|
- Rooms reconnecting instead of showing error popups
|
||||||
|
|
||||||
## v0.6.0 - 2023-04-04
|
## v0.6.0 - 2023-04-04
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Emoji support
|
- Emoji support
|
||||||
- `flake.nix`, making cove available as a nix flake
|
- `flake.nix`, making cove available as a nix flake
|
||||||
- `json-stream` room export format
|
- `json-stream` room export format
|
||||||
|
|
@ -111,31 +173,37 @@ Procedure when bumping the version number:
|
||||||
- `--verbose` flag
|
- `--verbose` flag
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Non-export info is now printed to stderr instead of stdout
|
- Non-export info is now printed to stderr instead of stdout
|
||||||
- Recognizes links without scheme (e.g. `euphoria.io` instead of `https://euphoria.io`)
|
- Recognizes links without scheme (e.g. `euphoria.io` instead of `https://euphoria.io`)
|
||||||
- Rooms waiting for reconnect are no longer sorted to bottom in default sort order
|
- Rooms waiting for reconnect are no longer sorted to bottom in default sort order
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Mentions not being stopped by `>`
|
- Mentions not being stopped by `>`
|
||||||
|
|
||||||
## v0.5.2 - 2023-01-14
|
## v0.5.2 - 2023-01-14
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Key binding to open present page
|
- Key binding to open present page
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Always connect to &rl2dev in ephemeral mode
|
- Always connect to &rl2dev in ephemeral mode
|
||||||
- Reduce amount of messages per &rl2dev log request
|
- Reduce amount of messages per &rl2dev log request
|
||||||
|
|
||||||
## v0.5.1 - 2022-11-27
|
## v0.5.1 - 2022-11-27
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Increase reconnect delay to one minute
|
- Increase reconnect delay to one minute
|
||||||
- Print errors that occurred while cove was running more compactly
|
- Print errors that occurred while cove was running more compactly
|
||||||
|
|
||||||
## v0.5.0 - 2022-09-26
|
## v0.5.0 - 2022-09-26
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Key bindings to navigate nick list
|
- Key bindings to navigate nick list
|
||||||
- Room deletion confirmation popup
|
- Room deletion confirmation popup
|
||||||
- Message inspection popup
|
- Message inspection popup
|
||||||
|
|
@ -144,10 +212,12 @@ Procedure when bumping the version number:
|
||||||
- `rooms_sort_order` config option
|
- `rooms_sort_order` config option
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Use nick changes to detect sessions for nick list
|
- Use nick changes to detect sessions for nick list
|
||||||
- Support Unicode 15
|
- Support Unicode 15
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Cursor being visible through popups
|
- Cursor being visible through popups
|
||||||
- Cursor in lists when highlighted item moves off-screen
|
- Cursor in lists when highlighted item moves off-screen
|
||||||
- User disappearing from nick list when only one of their sessions disconnects
|
- User disappearing from nick list when only one of their sessions disconnects
|
||||||
|
|
@ -155,6 +225,7 @@ Procedure when bumping the version number:
|
||||||
## v0.4.0 - 2022-09-01
|
## v0.4.0 - 2022-09-01
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Config file and `--config` cli option
|
- Config file and `--config` cli option
|
||||||
- `data_dir` config option
|
- `data_dir` config option
|
||||||
- `ephemeral` config option
|
- `ephemeral` config option
|
||||||
|
|
@ -170,14 +241,17 @@ Procedure when bumping the version number:
|
||||||
- Key bindings to view and open links in a message
|
- Key bindings to view and open links in a message
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Some key bindings in the rooms list
|
- Some key bindings in the rooms list
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Rooms being stuck in "Connecting" state
|
- Rooms being stuck in "Connecting" state
|
||||||
|
|
||||||
## v0.3.0 - 2022-08-22
|
## v0.3.0 - 2022-08-22
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Account login and logout
|
- Account login and logout
|
||||||
- Authentication dialog for password-protected rooms
|
- Authentication dialog for password-protected rooms
|
||||||
- Error popups in rooms when something goes wrong
|
- Error popups in rooms when something goes wrong
|
||||||
|
|
@ -185,10 +259,12 @@ Procedure when bumping the version number:
|
||||||
- Key binding to download more logs
|
- Key binding to download more logs
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Reduced amount of unnecessary redraws
|
- Reduced amount of unnecessary redraws
|
||||||
- Description of `export` CLI command
|
- Description of `export` CLI command
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Crash when connecting to nonexistent rooms
|
- Crash when connecting to nonexistent rooms
|
||||||
- Crash when connecting to rooms that require authentication
|
- Crash when connecting to rooms that require authentication
|
||||||
- Pasting multi-line strings into the editor
|
- Pasting multi-line strings into the editor
|
||||||
|
|
@ -196,15 +272,18 @@ Procedure when bumping the version number:
|
||||||
## v0.2.1 - 2022-08-11
|
## v0.2.1 - 2022-08-11
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Support for modifiers on special keys via the [kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/)
|
- Support for modifiers on special keys via the [kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Joining new rooms no longer crashes cove
|
- Joining new rooms no longer crashes cove
|
||||||
- Scrolling when exiting message editor
|
- Scrolling when exiting message editor
|
||||||
|
|
||||||
## v0.2.0 - 2022-08-10
|
## v0.2.0 - 2022-08-10
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- New messages are now marked as unseen
|
- New messages are now marked as unseen
|
||||||
- Sub-trees can now be folded
|
- Sub-trees can now be folded
|
||||||
- Support for pasting text into editors
|
- Support for pasting text into editors
|
||||||
|
|
@ -217,10 +296,12 @@ Procedure when bumping the version number:
|
||||||
- Support for exporting multiple/all rooms at once
|
- Support for exporting multiple/all rooms at once
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Reorganized export command
|
- Reorganized export command
|
||||||
- Slowed down room history download speed
|
- Slowed down room history download speed
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Chat rendering when deleting and re-joining a room
|
- Chat rendering when deleting and re-joining a room
|
||||||
- Spacing in some popups
|
- Spacing in some popups
|
||||||
|
|
||||||
|
|
|
||||||
76
CONFIG.md
76
CONFIG.md
|
|
@ -53,6 +53,14 @@ Available modifiers:
|
||||||
|
|
||||||
## Available options
|
## Available options
|
||||||
|
|
||||||
|
### `bell_on_mention`
|
||||||
|
|
||||||
|
**Required:** yes
|
||||||
|
**Type:** boolean
|
||||||
|
**Default:** `false`
|
||||||
|
|
||||||
|
Ring the bell (character 0x07) when you are mentioned in a room.
|
||||||
|
|
||||||
### `data_dir`
|
### `data_dir`
|
||||||
|
|
||||||
**Required:** no
|
**Required:** no
|
||||||
|
|
@ -93,9 +101,9 @@ Whether to automatically join this room on startup.
|
||||||
**Type:** boolean
|
**Type:** boolean
|
||||||
**Default:** `false`
|
**Default:** `false`
|
||||||
|
|
||||||
If `euph.rooms.<room>.username` is set, this will force cove to set the
|
If `euph.servers.<domain>.rooms.<room>.username` is set, this will force
|
||||||
username even if there is already a different username associated with
|
cove to set the username even if there is already a different username
|
||||||
the current session.
|
associated with the current session.
|
||||||
|
|
||||||
### `euph.servers.<domain>.rooms.<room>.password`
|
### `euph.servers.<domain>.rooms.<room>.password`
|
||||||
|
|
||||||
|
|
@ -529,6 +537,14 @@ Reply to message, inline if possible.
|
||||||
|
|
||||||
Reply opposite to normal reply.
|
Reply opposite to normal reply.
|
||||||
|
|
||||||
|
### `keys.tree.action.toggle_nick_emoji`
|
||||||
|
|
||||||
|
**Required:** yes
|
||||||
|
**Type:** key binding
|
||||||
|
**Default:** `"e"`
|
||||||
|
|
||||||
|
Toggle agent id based nick emoji.
|
||||||
|
|
||||||
### `keys.tree.action.toggle_seen`
|
### `keys.tree.action.toggle_seen`
|
||||||
|
|
||||||
**Required:** yes
|
**Required:** yes
|
||||||
|
|
@ -607,12 +623,11 @@ Move to root.
|
||||||
**Type:** boolean
|
**Type:** boolean
|
||||||
**Default:** `false`
|
**Default:** `false`
|
||||||
|
|
||||||
Whether to measure the width of characters as displayed by the terminal
|
Whether to measure the width of graphemes (i.e. characters) as displayed
|
||||||
emulator instead of guessing the width.
|
by the terminal emulator instead of estimating the width.
|
||||||
|
|
||||||
Enabling this makes rendering a bit slower but more accurate. The screen
|
Enabling this makes rendering a bit slower but more accurate. The screen
|
||||||
might also flash when encountering new characters (or, more accurately,
|
might also flash when encountering new graphemes.
|
||||||
graphemes).
|
|
||||||
|
|
||||||
See also the `--measure-widths` command line option.
|
See also the `--measure-widths` command line option.
|
||||||
|
|
||||||
|
|
@ -656,18 +671,41 @@ order of priority):
|
||||||
|
|
||||||
Time zone that chat timestamps should be displayed in.
|
Time zone that chat timestamps should be displayed in.
|
||||||
|
|
||||||
This option is interpreted as a POSIX TZ string. It is described here in
|
This option can either be the string `"localtime"`, a [POSIX TZ string],
|
||||||
further detail:
|
or a [tz identifier] from the [tz database].
|
||||||
<https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html>
|
|
||||||
|
|
||||||
On a normal system, the string `"localtime"` as well as any value from
|
When not set or when set to `"localtime"`, cove attempts to use your
|
||||||
the "TZ identifier" column of the following wikipedia article should be
|
system's configured time zone, falling back to UTC.
|
||||||
valid TZ strings:
|
|
||||||
<https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>
|
|
||||||
|
|
||||||
If the `TZ` environment variable exists, it overrides this option. If
|
When the string begins with a colon or doesn't match the a POSIX TZ
|
||||||
neither exist, cove uses the system's local time zone.
|
string format, it is interpreted as a tz identifier and looked up in
|
||||||
|
your system's tz database (or a bundled tz database on Windows).
|
||||||
|
|
||||||
**Warning:** On Windows, cove can't get the local time zone and uses UTC
|
If the `TZ` environment variable exists, it overrides this option.
|
||||||
instead. However, you can still specify a path to a tz data file or a
|
|
||||||
custom time zone string.
|
[POSIX TZ string]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03
|
||||||
|
[tz identifier]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||||
|
[tz database]: https://en.wikipedia.org/wiki/Tz_database
|
||||||
|
|
||||||
|
### `width_estimation_method`
|
||||||
|
|
||||||
|
**Required:** yes
|
||||||
|
**Type:** string
|
||||||
|
**Values:** `"legacy"`, `"unicode"`
|
||||||
|
**Default:** `"legacy"`
|
||||||
|
|
||||||
|
How to estimate the width of graphemes (i.e. characters) as displayed by
|
||||||
|
the terminal emulator.
|
||||||
|
|
||||||
|
`"legacy"`: Use a legacy method that should mostly work on most terminal
|
||||||
|
emulators. This method will never be correct in all cases since every
|
||||||
|
terminal emulator handles grapheme widths slightly differently. However,
|
||||||
|
those cases are usually rare (unless you view a lot of emoji).
|
||||||
|
|
||||||
|
`"unicode"`: Use the unicode standard in a best-effort manner to
|
||||||
|
determine grapheme widths. Some terminals (e.g. ghostty) can make use of
|
||||||
|
this.
|
||||||
|
|
||||||
|
This method is used when `measure_widths` is set to `false`.
|
||||||
|
|
||||||
|
See also the `--width-estimation-method` command line option.
|
||||||
|
|
|
||||||
275
Cargo.lock
generated
275
Cargo.lock
generated
|
|
@ -90,15 +90,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.96"
|
version = "1.0.97"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4"
|
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.86"
|
version = "0.1.87"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d"
|
checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -113,27 +113,25 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-lc-rs"
|
name = "aws-lc-rs"
|
||||||
version = "1.12.2"
|
version = "1.12.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c2b7ddaa2c56a367ad27a094ad8ef4faacf8a617c2575acb2ba88949df999ca"
|
checksum = "dabb68eb3a7aa08b46fddfd59a3d55c978243557a90ab804769f7e20e67d2b01"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-lc-sys",
|
"aws-lc-sys",
|
||||||
"paste",
|
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-lc-sys"
|
name = "aws-lc-sys"
|
||||||
version = "0.25.1"
|
version = "0.27.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "54ac4f13dad353b209b34cbec082338202cbc01c8f00336b55c750c13ac91f8f"
|
checksum = "6bbe221bbf523b625a4dd8585c7f38166e31167ec2ca98051dbcb4c3b6e825d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bindgen",
|
"bindgen",
|
||||||
"cc",
|
"cc",
|
||||||
"cmake",
|
"cmake",
|
||||||
"dunce",
|
"dunce",
|
||||||
"fs_extra",
|
"fs_extra",
|
||||||
"paste",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -176,9 +174,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.8.0"
|
version = "2.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
|
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
|
|
@ -189,17 +187,11 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "byteorder"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.10.0"
|
version = "1.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
|
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "caseless"
|
name = "caseless"
|
||||||
|
|
@ -212,9 +204,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.14"
|
version = "1.2.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9"
|
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jobserver",
|
"jobserver",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
@ -249,9 +241,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.30"
|
version = "4.5.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d"
|
checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
|
|
@ -259,9 +251,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.30"
|
version = "4.5.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c"
|
checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
|
|
@ -271,9 +263,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.28"
|
version = "4.5.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
|
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -330,7 +322,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cove"
|
name = "cove"
|
||||||
version = "0.8.3"
|
version = "0.9.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
|
@ -344,7 +336,6 @@ dependencies = [
|
||||||
"jiff",
|
"jiff",
|
||||||
"linkify",
|
"linkify",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
|
||||||
"open",
|
"open",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
|
|
@ -359,7 +350,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cove-config"
|
name = "cove-config"
|
||||||
version = "0.8.3"
|
version = "0.9.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cove-input",
|
"cove-input",
|
||||||
"cove-macro",
|
"cove-macro",
|
||||||
|
|
@ -370,7 +361,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cove-input"
|
name = "cove-input"
|
||||||
version = "0.8.3"
|
version = "0.9.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cove-macro",
|
"cove-macro",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
|
@ -384,7 +375,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cove-macro"
|
name = "cove-macro"
|
||||||
version = "0.8.3"
|
version = "0.9.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -410,7 +401,7 @@ dependencies = [
|
||||||
"crossterm_winapi",
|
"crossterm_winapi",
|
||||||
"mio",
|
"mio",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"rustix",
|
"rustix 0.38.44",
|
||||||
"signal-hook",
|
"signal-hook",
|
||||||
"signal-hook-mio",
|
"signal-hook-mio",
|
||||||
"winapi",
|
"winapi",
|
||||||
|
|
@ -499,9 +490,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.13.0"
|
version = "1.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
|
|
@ -521,7 +512,8 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "euphoxide"
|
name = "euphoxide"
|
||||||
version = "0.5.1"
|
version = "0.6.1"
|
||||||
|
source = "git+https://github.com/Garmelon/euphoxide.git?tag=v0.6.1#7a292c429ad44aa6aa52fc381e3168841d6303b0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"caseless",
|
"caseless",
|
||||||
|
|
@ -686,9 +678,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "1.2.0"
|
version = "1.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
|
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
|
|
@ -697,15 +689,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.10.0"
|
version = "1.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a"
|
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.7.1"
|
version = "2.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
|
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.15.2",
|
"hashbrown 0.15.2",
|
||||||
|
|
@ -747,16 +739,17 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.14"
|
version = "1.0.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jiff"
|
name = "jiff"
|
||||||
version = "0.2.1"
|
version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3590fea8e9e22d449600c9bbd481a8163bef223e4ff938e5f55899f8cf1adb93"
|
checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"jiff-static",
|
||||||
"jiff-tzdb-platform",
|
"jiff-tzdb-platform",
|
||||||
"log",
|
"log",
|
||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
|
|
@ -766,10 +759,21 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jiff-tzdb"
|
name = "jiff-static"
|
||||||
version = "0.1.2"
|
version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf2cec2f5d266af45a071ece48b1fb89f3b00b2421ac3a5fe10285a6caaa60d3"
|
checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff-tzdb"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "962e1dfe9b2d75a84536cf5bf5eaaa4319aa7906c7160134a22883ac316d5f31"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jiff-tzdb-platform"
|
name = "jiff-tzdb-platform"
|
||||||
|
|
@ -803,9 +807,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.169"
|
version = "0.2.171"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libloading"
|
name = "libloading"
|
||||||
|
|
@ -833,6 +837,7 @@ version = "0.28.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f"
|
checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"cc",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
@ -852,6 +857,12 @@ version = "0.4.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.12"
|
version = "0.4.12"
|
||||||
|
|
@ -864,9 +875,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.25"
|
version = "0.4.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
|
|
@ -882,9 +893,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.4"
|
version = "0.8.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b"
|
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler2",
|
"adler2",
|
||||||
]
|
]
|
||||||
|
|
@ -937,9 +948,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.20.3"
|
version = "1.21.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
|
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "open"
|
name = "open"
|
||||||
|
|
@ -996,12 +1007,6 @@ dependencies = [
|
||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "paste"
|
|
||||||
version = "1.0.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathdiff"
|
name = "pathdiff"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
|
|
@ -1022,15 +1027,15 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkg-config"
|
name = "pkg-config"
|
||||||
version = "0.3.31"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
version = "1.10.0"
|
version = "1.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
|
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic-util"
|
name = "portable-atomic-util"
|
||||||
|
|
@ -1049,18 +1054,18 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.20"
|
version = "0.2.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy 0.7.35",
|
"zerocopy 0.8.23",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
version = "0.2.29"
|
version = "0.2.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac"
|
checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"syn",
|
"syn",
|
||||||
|
|
@ -1068,18 +1073,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.93"
|
version = "1.0.94"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.38"
|
version = "1.0.40"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
@ -1092,7 +1097,7 @@ checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand_chacha",
|
"rand_chacha",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
"zerocopy 0.8.20",
|
"zerocopy 0.8.23",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1107,19 +1112,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
version = "0.9.1"
|
version = "0.9.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a88e0da7a2c97baa202165137c158d0a2e824ac465d13d81046727b34cb247d3"
|
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.3.1",
|
"getrandom 0.3.1",
|
||||||
"zerocopy 0.8.20",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.8"
|
version = "0.5.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
|
checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
@ -1166,9 +1170,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.17.9"
|
version = "0.17.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24"
|
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
|
@ -1214,7 +1218,20 @@ dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys 0.4.15",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys 0.9.3",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1265,9 +1282,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.19"
|
version = "1.0.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
|
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schannel"
|
name = "schannel"
|
||||||
|
|
@ -1309,9 +1326,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.218"
|
version = "1.0.219"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
@ -1328,9 +1345,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.218"
|
version = "1.0.219"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -1349,9 +1366,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.139"
|
version = "1.0.140"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
|
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
|
@ -1454,9 +1471,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.98"
|
version = "2.0.100"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -1465,32 +1482,31 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.17.1"
|
version = "3.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230"
|
checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom 0.3.1",
|
"getrandom 0.3.1",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix 1.0.2",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.11"
|
version = "2.0.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
|
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "2.0.11"
|
version = "2.0.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
|
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -1499,9 +1515,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.37"
|
version = "0.3.39"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
|
checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
"itoa",
|
"itoa",
|
||||||
|
|
@ -1514,15 +1530,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time-core"
|
name = "time-core"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time-macros"
|
name = "time-macros"
|
||||||
version = "0.2.19"
|
version = "0.2.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
|
checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-conv",
|
"num-conv",
|
||||||
"time-core",
|
"time-core",
|
||||||
|
|
@ -1530,9 +1546,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.8.1"
|
version = "1.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8"
|
checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tinyvec_macros",
|
"tinyvec_macros",
|
||||||
]
|
]
|
||||||
|
|
@ -1545,9 +1561,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.43.0"
|
version = "1.44.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
|
checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|
@ -1574,9 +1590,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-rustls"
|
name = "tokio-rustls"
|
||||||
version = "0.26.1"
|
version = "0.26.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37"
|
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
@ -1645,7 +1661,8 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toss"
|
name = "toss"
|
||||||
version = "0.3.0"
|
version = "0.3.4"
|
||||||
|
source = "git+https://github.com/Garmelon/toss.git?tag=v0.3.4#57aa8c59308f6f0aa82bde415a42b56c3d6f7c4d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
|
@ -1681,9 +1698,9 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.17"
|
version = "1.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
|
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-linebreak"
|
name = "unicode-linebreak"
|
||||||
|
|
@ -1732,7 +1749,8 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vault"
|
name = "vault"
|
||||||
version = "0.5.0"
|
version = "0.4.0"
|
||||||
|
source = "git+https://github.com/Garmelon/vault.git?tag=v0.4.0#a53254d2e787d15fd2d00584fddf9b84e79572ee"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
@ -1774,7 +1792,7 @@ dependencies = [
|
||||||
"either",
|
"either",
|
||||||
"home",
|
"home",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix 0.38.44",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1883,9 +1901,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.7.3"
|
version = "0.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1"
|
checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
@ -1905,17 +1923,16 @@ version = "0.7.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
|
||||||
"zerocopy-derive 0.7.35",
|
"zerocopy-derive 0.7.35",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.20"
|
version = "0.8.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dde3bb8c68a8f3f1ed4ac9221aad6b10cece3e60a8e2ea54a6a2dec806d0084c"
|
checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive 0.8.20",
|
"zerocopy-derive 0.8.23",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1931,9 +1948,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.20"
|
version = "0.8.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eea57037071898bf96a6da35fd626f4f27e9cee3ead2a6c703cf09d472b2e700"
|
checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
||||||
59
Cargo.toml
59
Cargo.toml
|
|
@ -1,55 +1,49 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "3"
|
||||||
members = ["cove", "cove-*"]
|
members = ["cove", "cove-*"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.8.3"
|
version = "0.9.3"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
anyhow = "1.0.96"
|
anyhow = "1.0.97"
|
||||||
async-trait = "0.1.86"
|
async-trait = "0.1.87"
|
||||||
clap = { version = "4.5.30", features = ["derive", "deprecated"] }
|
clap = { version = "4.5.32", features = ["derive", "deprecated"] }
|
||||||
cookie = "0.18.1"
|
cookie = "0.18.1"
|
||||||
crossterm = "0.28.1"
|
crossterm = "0.28.1"
|
||||||
directories = "6.0.0"
|
directories = "6.0.0"
|
||||||
edit = "0.1.5"
|
edit = "0.1.5"
|
||||||
jiff = "0.2.1"
|
jiff = "0.2.4"
|
||||||
linkify = "0.10.0"
|
linkify = "0.10.0"
|
||||||
log = { version = "0.4.25", features = ["std"] }
|
log = { version = "0.4.26", features = ["std"] }
|
||||||
once_cell = "1.20.2"
|
|
||||||
open = "5.3.2"
|
open = "5.3.2"
|
||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
proc-macro2 = "1.0.93"
|
proc-macro2 = "1.0.94"
|
||||||
quote = "1.0.38"
|
quote = "1.0.40"
|
||||||
rusqlite = { version = "0.31.0", features = [
|
rusqlite = { version = "0.31.0", features = ["bundled", "time"] }
|
||||||
# "bundled",
|
|
||||||
"time",
|
|
||||||
] }
|
|
||||||
rustls = "0.23.23"
|
rustls = "0.23.23"
|
||||||
serde = { version = "1.0.218", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_either = "0.2.1"
|
serde_either = "0.2.1"
|
||||||
serde_json = "1.0.139"
|
serde_json = "1.0.140"
|
||||||
syn = "2.0.98"
|
syn = "2.0.100"
|
||||||
thiserror = "2.0.11"
|
thiserror = "2.0.12"
|
||||||
tokio = { version = "1.43.0", features = ["full"] }
|
tokio = { version = "1.44.1", features = ["full"] }
|
||||||
toml = "0.8.20"
|
toml = "0.8.20"
|
||||||
unicode-width = "0.2.0"
|
unicode-width = "0.2.0"
|
||||||
|
|
||||||
[workspace.dependencies.euphoxide]
|
[workspace.dependencies.euphoxide]
|
||||||
path = "../euphoxide"
|
git = "https://github.com/Garmelon/euphoxide.git"
|
||||||
# git = "https://github.com/Garmelon/euphoxide.git"
|
tag = "v0.6.1"
|
||||||
features = ["bot"]
|
features = ["bot"]
|
||||||
|
|
||||||
[workspace.dependencies.toss]
|
[workspace.dependencies.toss]
|
||||||
path = "../toss"
|
git = "https://github.com/Garmelon/toss.git"
|
||||||
# git = "https://github.com/Garmelon/toss.git"
|
tag = "v0.3.4"
|
||||||
# tag = "v0.2.3"
|
|
||||||
|
|
||||||
[workspace.dependencies.vault]
|
[workspace.dependencies.vault]
|
||||||
path = "../vault"
|
git = "https://github.com/Garmelon/vault.git"
|
||||||
# git = "https://github.com/Garmelon/vault.git"
|
tag = "v0.4.0"
|
||||||
# tag = "v0.5.0"
|
|
||||||
features = ["tokio"]
|
features = ["tokio"]
|
||||||
|
|
||||||
[workspace.lints]
|
[workspace.lints]
|
||||||
|
|
@ -74,14 +68,5 @@ rust.unused_qualifications = "warn"
|
||||||
# Clippy
|
# Clippy
|
||||||
clippy.use_self = "warn"
|
clippy.use_self = "warn"
|
||||||
|
|
||||||
|
|
||||||
[profile.dev.package."*"]
|
[profile.dev.package."*"]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
||||||
# For profiling
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
debug = 1
|
|
||||||
|
|
||||||
[rust]
|
|
||||||
debuginfo-level = 1
|
|
||||||
|
|
|
||||||
63
README.md
63
README.md
|
|
@ -7,6 +7,11 @@ real-time chat platform.
|
||||||
|
|
||||||
It runs on Linux, Windows, and macOS.
|
It runs on Linux, Windows, and macOS.
|
||||||
|
|
||||||
|
## Installing cove
|
||||||
|
|
||||||
|
Download a binary of your choice from the
|
||||||
|
[latest release on GitHub](https://github.com/Garmelon/cove/releases/latest).
|
||||||
|
|
||||||
## Using cove
|
## Using cove
|
||||||
|
|
||||||
To start cove, simply run `cove` in your terminal. For more info about the
|
To start cove, simply run `cove` in your terminal. For more info about the
|
||||||
|
|
@ -26,61 +31,3 @@ file or via `cove help-config`.
|
||||||
When launched, cove prints the location it is loading its config file from. To
|
When launched, cove prints the location it is loading its config file from. To
|
||||||
configure cove, create a config file at that location. This location can be
|
configure cove, create a config file at that location. This location can be
|
||||||
changed via the `--config` command line option.
|
changed via the `--config` command line option.
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
At this point, cove is not available via any package manager.
|
|
||||||
|
|
||||||
Cove is available as a Nix Flake. To try it out, you can use
|
|
||||||
```bash
|
|
||||||
$ nix run --override-input nixpkgs nixpkgs github:Garmelon/cove/latest
|
|
||||||
```
|
|
||||||
|
|
||||||
## Manual installation
|
|
||||||
|
|
||||||
This section contains instructions on how to install cove by compiling it yourself.
|
|
||||||
It doesn't assume you know how to program, but it does assume basic familiarity with the command line on your platform of choice.
|
|
||||||
Cove runs in the terminal, after all.
|
|
||||||
|
|
||||||
### Installing rustup
|
|
||||||
|
|
||||||
Cove is written in Rust, so the first step is to install rustup. Either install
|
|
||||||
it from your package manager of choice (if you have one) or use the
|
|
||||||
[installer](https://rustup.rs/).
|
|
||||||
|
|
||||||
Test your installation by running `rustup --version` and `cargo --version`. If
|
|
||||||
rustup is installed correctly, both of these should show a version number.
|
|
||||||
|
|
||||||
Cove is designed on the current version of the stable toolchain. If cove doesn't
|
|
||||||
compile, you can try switching to the stable toolchain and updating it using the
|
|
||||||
following commands:
|
|
||||||
```bash
|
|
||||||
$ rustup default stable
|
|
||||||
$ rustup update
|
|
||||||
```
|
|
||||||
|
|
||||||
### Installing cove
|
|
||||||
|
|
||||||
To install or update to the latest release of cove, run the following command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ cargo install --force --git https://github.com/Garmelon/cove --branch latest
|
|
||||||
```
|
|
||||||
|
|
||||||
If you like to live dangerously and want to install or update to the latest,
|
|
||||||
bleeding-edge, possibly-broken commit from the repo's main branch, run the
|
|
||||||
following command.
|
|
||||||
|
|
||||||
**Warning:** This could corrupt your vault. Make sure to make a backup before
|
|
||||||
running the command.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ cargo install --force --git https://github.com/Garmelon/cove
|
|
||||||
```
|
|
||||||
|
|
||||||
To install a specific version of cove, run the following command and substitute
|
|
||||||
in the full version you want to install:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ cargo install --force --git https://github.com/Garmelon/cove --tag v0.1.0
|
|
||||||
```
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
//! Auto-generate markdown documentation.
|
//! Auto-generate markdown documentation.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use cove_input::KeyBinding;
|
use cove_input::KeyBinding;
|
||||||
pub use cove_macro::Document;
|
pub use cove_macro::Document;
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,7 @@ default_bindings! {
|
||||||
pub fn mark_older_seen => ["ctrl+s"];
|
pub fn mark_older_seen => ["ctrl+s"];
|
||||||
pub fn info => ["i"];
|
pub fn info => ["i"];
|
||||||
pub fn links => ["I"];
|
pub fn links => ["I"];
|
||||||
|
pub fn toggle_nick_emoji => ["e"];
|
||||||
pub fn increase_caesar => ["c"];
|
pub fn increase_caesar => ["c"];
|
||||||
pub fn decrease_caesar => ["C"];
|
pub fn decrease_caesar => ["C"];
|
||||||
}
|
}
|
||||||
|
|
@ -356,6 +357,9 @@ pub struct TreeAction {
|
||||||
/// List links found in message.
|
/// List links found in message.
|
||||||
#[serde(default = "default::tree_action::links")]
|
#[serde(default = "default::tree_action::links")]
|
||||||
pub links: KeyBinding,
|
pub links: KeyBinding,
|
||||||
|
/// Toggle agent id based nick emoji.
|
||||||
|
#[serde(default = "default::tree_action::toggle_nick_emoji")]
|
||||||
|
pub toggle_nick_emoji: KeyBinding,
|
||||||
/// Increase caesar cipher rotation.
|
/// Increase caesar cipher rotation.
|
||||||
#[serde(default = "default::tree_action::increase_caesar")]
|
#[serde(default = "default::tree_action::increase_caesar")]
|
||||||
pub increase_caesar: KeyBinding,
|
pub increase_caesar: KeyBinding,
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
|
use std::{
|
||||||
|
fs,
|
||||||
|
io::{self, ErrorKind},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use doc::Document;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub use crate::{euph::*, keys::*};
|
||||||
|
|
||||||
pub mod doc;
|
pub mod doc;
|
||||||
mod euph;
|
mod euph;
|
||||||
mod keys;
|
mod keys;
|
||||||
|
|
||||||
use std::io::ErrorKind;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::{fs, io};
|
|
||||||
|
|
||||||
use doc::Document;
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
pub use crate::euph::*;
|
|
||||||
pub use crate::keys::*;
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("failed to read config file")]
|
#[error("failed to read config file")]
|
||||||
|
|
@ -20,6 +21,14 @@ pub enum Error {
|
||||||
Toml(#[from] toml::de::Error),
|
Toml(#[from] toml::de::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, Document)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum WidthEstimationMethod {
|
||||||
|
#[default]
|
||||||
|
Legacy,
|
||||||
|
Unicode,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Document)]
|
#[derive(Debug, Default, Deserialize, Document)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// The directory that cove stores its data in when not running in ephemeral
|
/// The directory that cove stores its data in when not running in ephemeral
|
||||||
|
|
@ -40,12 +49,29 @@ pub struct Config {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub ephemeral: bool,
|
pub ephemeral: bool,
|
||||||
|
|
||||||
/// Whether to measure the width of characters as displayed by the terminal
|
/// How to estimate the width of graphemes (i.e. characters) as displayed by
|
||||||
/// emulator instead of guessing the width.
|
/// the terminal emulator.
|
||||||
|
///
|
||||||
|
/// `"legacy"`: Use a legacy method that should mostly work on most terminal
|
||||||
|
/// emulators. This method will never be correct in all cases since every
|
||||||
|
/// terminal emulator handles grapheme widths slightly differently. However,
|
||||||
|
/// those cases are usually rare (unless you view a lot of emoji).
|
||||||
|
///
|
||||||
|
/// `"unicode"`: Use the unicode standard in a best-effort manner to
|
||||||
|
/// determine grapheme widths. Some terminals (e.g. ghostty) can make use of
|
||||||
|
/// this.
|
||||||
|
///
|
||||||
|
/// This method is used when `measure_widths` is set to `false`.
|
||||||
|
///
|
||||||
|
/// See also the `--width-estimation-method` command line option.
|
||||||
|
#[serde(default)]
|
||||||
|
pub width_estimation_method: WidthEstimationMethod,
|
||||||
|
|
||||||
|
/// Whether to measure the width of graphemes (i.e. characters) as displayed
|
||||||
|
/// by the terminal emulator instead of estimating the width.
|
||||||
///
|
///
|
||||||
/// Enabling this makes rendering a bit slower but more accurate. The screen
|
/// Enabling this makes rendering a bit slower but more accurate. The screen
|
||||||
/// might also flash when encountering new characters (or, more accurately,
|
/// might also flash when encountering new graphemes.
|
||||||
/// graphemes).
|
|
||||||
///
|
///
|
||||||
/// See also the `--measure-widths` command line option.
|
/// See also the `--measure-widths` command line option.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|
@ -74,6 +100,10 @@ pub struct Config {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub rooms_sort_order: RoomsSortOrder,
|
pub rooms_sort_order: RoomsSortOrder,
|
||||||
|
|
||||||
|
/// Ring the bell (character 0x07) when you are mentioned in a room.
|
||||||
|
#[serde(default)]
|
||||||
|
pub bell_on_mention: bool,
|
||||||
|
|
||||||
/// Time zone that chat timestamps should be displayed in.
|
/// Time zone that chat timestamps should be displayed in.
|
||||||
///
|
///
|
||||||
/// This option can either be the string `"localtime"`, a [POSIX TZ string],
|
/// This option can either be the string `"localtime"`, a [POSIX TZ string],
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
use std::fmt;
|
use std::{fmt, num::ParseIntError, str::FromStr};
|
||||||
use std::num::ParseIntError;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
use serde::{de::Error, Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
|
||||||
use serde::{Serialize, Serializer};
|
|
||||||
use serde_either::SingleOrVec;
|
use serde_either::SingleOrVec;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
@ -117,7 +114,7 @@ impl KeyPress {
|
||||||
"alt" if !self.alt => self.alt = true,
|
"alt" if !self.alt => self.alt = true,
|
||||||
"any" if !self.shift && !self.ctrl && !self.alt => self.any = true,
|
"any" if !self.shift && !self.ctrl && !self.alt => self.any = true,
|
||||||
m @ ("shift" | "ctrl" | "alt" | "any") => {
|
m @ ("shift" | "ctrl" | "alt" | "any") => {
|
||||||
return Err(ParseKeysError::ConflictingModifier(m.to_string()))
|
return Err(ParseKeysError::ConflictingModifier(m.to_string()));
|
||||||
}
|
}
|
||||||
m => return Err(ParseKeysError::UnknownModifier(m.to_string())),
|
m => return Err(ParseKeysError::UnknownModifier(m.to_string())),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
mod keys;
|
use std::{io, sync::Arc};
|
||||||
|
|
||||||
use std::io;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub use cove_macro::KeyGroup;
|
pub use cove_macro::KeyGroup;
|
||||||
use crossterm::event::{Event, KeyEvent, KeyEventKind};
|
use crossterm::event::{Event, KeyEvent, KeyEventKind};
|
||||||
|
|
@ -10,6 +7,8 @@ use toss::{Frame, Terminal, WidthDb};
|
||||||
|
|
||||||
pub use crate::keys::*;
|
pub use crate::keys::*;
|
||||||
|
|
||||||
|
mod keys;
|
||||||
|
|
||||||
pub struct KeyBindingInfo<'a> {
|
pub struct KeyBindingInfo<'a> {
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub binding: &'a KeyBinding,
|
pub binding: &'a KeyBinding,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::spanned::Spanned;
|
use syn::{Data, DataEnum, DataStruct, DeriveInput, Field, Ident, LitStr, spanned::Spanned};
|
||||||
use syn::{Data, DataEnum, DataStruct, DeriveInput, Field, Ident, LitStr};
|
|
||||||
|
|
||||||
use crate::util::{self, SerdeDefault};
|
use crate::util::{self, SerdeDefault};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::spanned::Spanned;
|
use syn::{Data, DeriveInput, spanned::Spanned};
|
||||||
use syn::{Data, DeriveInput};
|
|
||||||
|
|
||||||
use crate::util;
|
use crate::util;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use syn::{parse_macro_input, DeriveInput};
|
use syn::{DeriveInput, parse_macro_input};
|
||||||
|
|
||||||
mod document;
|
mod document;
|
||||||
mod key_group;
|
mod key_group;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::parse::Parse;
|
use syn::{
|
||||||
use syn::punctuated::Punctuated;
|
Attribute, Expr, ExprLit, ExprPath, Field, Lit, LitStr, Path, Token, Type, parse::Parse,
|
||||||
use syn::{Attribute, Expr, ExprLit, ExprPath, Field, Lit, LitStr, Path, Token, Type};
|
punctuated::Punctuated,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn bail<T>(span: Span, message: &str) -> syn::Result<T> {
|
pub fn bail<T>(span: Span, message: &str) -> syn::Result<T> {
|
||||||
Err(syn::Error::new(span, message))
|
Err(syn::Error::new(span, message))
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ euphoxide.workspace = true
|
||||||
jiff.workspace = true
|
jiff.workspace = true
|
||||||
linkify.workspace = true
|
linkify.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
once_cell.workspace = true
|
|
||||||
open.workspace = true
|
open.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
rusqlite.workspace = true
|
rusqlite.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
mod room;
|
pub use highlight::*;
|
||||||
mod small_message;
|
|
||||||
mod util;
|
|
||||||
|
|
||||||
pub use room::*;
|
pub use room::*;
|
||||||
pub use small_message::*;
|
pub use small_message::*;
|
||||||
pub use util::*;
|
pub use util::*;
|
||||||
|
|
||||||
|
mod highlight;
|
||||||
|
mod room;
|
||||||
|
mod small_message;
|
||||||
|
mod util;
|
||||||
|
|
|
||||||
211
cove/src/euph/highlight.rs
Normal file
211
cove/src/euph/highlight.rs
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
|
use crossterm::style::Stylize;
|
||||||
|
use toss::{Style, Styled};
|
||||||
|
|
||||||
|
use crate::euph::util;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum SpanType {
|
||||||
|
Mention,
|
||||||
|
Room,
|
||||||
|
Emoji,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nick_char(ch: char) -> bool {
|
||||||
|
// Closely following the heim mention regex:
|
||||||
|
// https://github.com/euphoria-io/heim/blob/978c921063e6b06012fc8d16d9fbf1b3a0be1191/client/lib/stores/chat.js#L14-L15
|
||||||
|
// `>` has been experimentally confirmed to delimit mentions as well.
|
||||||
|
match ch {
|
||||||
|
',' | '.' | '!' | '?' | ';' | '&' | '<' | '>' | '\'' | '"' => false,
|
||||||
|
_ => !ch.is_whitespace(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn room_char(ch: char) -> bool {
|
||||||
|
// Basically just \w, see also
|
||||||
|
// https://github.com/euphoria-io/heim/blob/978c921063e6b06012fc8d16d9fbf1b3a0be1191/client/lib/ui/MessageText.js#L66
|
||||||
|
ch.is_ascii_alphanumeric() || ch == '_'
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SpanFinder<'a> {
|
||||||
|
content: &'a str,
|
||||||
|
|
||||||
|
span: Option<(SpanType, usize)>,
|
||||||
|
room_or_mention_possible: bool,
|
||||||
|
|
||||||
|
result: Vec<(SpanType, Range<usize>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SpanFinder<'a> {
|
||||||
|
fn is_valid_span(&self, span: SpanType, range: Range<usize>) -> bool {
|
||||||
|
let text = &self.content[range.start..range.end];
|
||||||
|
match span {
|
||||||
|
SpanType::Mention => range.len() > 1 && text.starts_with('@'),
|
||||||
|
SpanType::Room => range.len() > 1 && text.starts_with('&'),
|
||||||
|
SpanType::Emoji => {
|
||||||
|
if range.len() <= 2 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(name) = Some(text)
|
||||||
|
.and_then(|it| it.strip_prefix(':'))
|
||||||
|
.and_then(|it| it.strip_suffix(':'))
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
util::EMOJI.get(name).is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close_span(&mut self, end: usize) {
|
||||||
|
let Some((span, start)) = self.span else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if self.is_valid_span(span, start..end) {
|
||||||
|
self.result.push((span, start..end));
|
||||||
|
}
|
||||||
|
self.span = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_span(&mut self, span: SpanType, start: usize) {
|
||||||
|
self.close_span(start);
|
||||||
|
self.span = Some((span, start))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn step(&mut self, idx: usize, char: char) {
|
||||||
|
match (char, self.span) {
|
||||||
|
('@', Some((SpanType::Mention, _))) => {} // Continue the mention
|
||||||
|
('@', _) if self.room_or_mention_possible => self.open_span(SpanType::Mention, idx),
|
||||||
|
('&', _) if self.room_or_mention_possible => self.open_span(SpanType::Room, idx),
|
||||||
|
(':', None) => self.open_span(SpanType::Emoji, idx),
|
||||||
|
(':', Some((SpanType::Emoji, _))) => self.close_span(idx + 1),
|
||||||
|
(c, Some((SpanType::Mention, _))) if !nick_char(c) => self.close_span(idx),
|
||||||
|
(c, Some((SpanType::Room, _))) if !room_char(c) => self.close_span(idx),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// More permissive than the heim web client
|
||||||
|
self.room_or_mention_possible = !char.is_alphanumeric();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find(content: &'a str) -> Vec<(SpanType, Range<usize>)> {
|
||||||
|
let mut this = Self {
|
||||||
|
content,
|
||||||
|
span: None,
|
||||||
|
room_or_mention_possible: true,
|
||||||
|
result: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (idx, char) in content.char_indices() {
|
||||||
|
this.step(idx, char);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.close_span(content.len());
|
||||||
|
|
||||||
|
this.result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_spans(content: &str) -> Vec<(SpanType, Range<usize>)> {
|
||||||
|
SpanFinder::find(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Highlight spans in a string.
|
||||||
|
///
|
||||||
|
/// The list of spans must be non-overlapping and in ascending order.
|
||||||
|
///
|
||||||
|
/// If `exact` is specified, colon-delimited emoji are not replaced with their
|
||||||
|
/// unicode counterparts.
|
||||||
|
pub fn apply_spans(
|
||||||
|
content: &str,
|
||||||
|
spans: &[(SpanType, Range<usize>)],
|
||||||
|
base: Style,
|
||||||
|
exact: bool,
|
||||||
|
) -> Styled {
|
||||||
|
let mut result = Styled::default();
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
for (span, range) in spans {
|
||||||
|
assert!(i <= range.start);
|
||||||
|
assert!(range.end <= content.len());
|
||||||
|
|
||||||
|
if i < range.start {
|
||||||
|
result = result.then(&content[i..range.start], base);
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = &content[range.start..range.end];
|
||||||
|
result = match span {
|
||||||
|
SpanType::Mention if exact => result.and_then(util::style_mention_exact(text, base)),
|
||||||
|
SpanType::Mention => result.and_then(util::style_mention(text, base)),
|
||||||
|
SpanType::Room => result.then(text, base.blue().bold()),
|
||||||
|
SpanType::Emoji if exact => result.then(text, base.magenta()),
|
||||||
|
SpanType::Emoji => {
|
||||||
|
let name = text.strip_prefix(':').unwrap_or(text);
|
||||||
|
let name = name.strip_suffix(':').unwrap_or(name);
|
||||||
|
if let Some(Some(replacement)) = util::EMOJI.get(name) {
|
||||||
|
result.then(replacement, base)
|
||||||
|
} else {
|
||||||
|
result.then(text, base.magenta())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
i = range.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if i < content.len() {
|
||||||
|
result = result.then(&content[i..], base);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Highlight an euphoria message's content.
|
||||||
|
///
|
||||||
|
/// If `exact` is specified, colon-delimited emoji are not replaced with their
|
||||||
|
/// unicode counterparts.
|
||||||
|
pub fn highlight(content: &str, base: Style, exact: bool) -> Styled {
|
||||||
|
apply_spans(content, &find_spans(content), base, exact)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use crate::euph::SpanType;
|
||||||
|
|
||||||
|
use super::find_spans;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mentions() {
|
||||||
|
assert_eq!(find_spans("@foo"), vec![(SpanType::Mention, 0..4)]);
|
||||||
|
assert_eq!(find_spans("&@foo"), vec![(SpanType::Mention, 1..5)]);
|
||||||
|
assert_eq!(find_spans("a @foo b"), vec![(SpanType::Mention, 2..6)]);
|
||||||
|
assert_eq!(find_spans("@@foo@@"), vec![(SpanType::Mention, 0..7)]);
|
||||||
|
assert_eq!(find_spans("a @b@c d"), vec![(SpanType::Mention, 2..6)]);
|
||||||
|
assert_eq!(
|
||||||
|
find_spans("a @b @c d"),
|
||||||
|
vec![(SpanType::Mention, 2..4), (SpanType::Mention, 5..7)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rooms() {
|
||||||
|
assert_eq!(find_spans("&foo"), vec![(SpanType::Room, 0..4)]);
|
||||||
|
assert_eq!(find_spans("@&foo"), vec![(SpanType::Room, 1..5)]);
|
||||||
|
assert_eq!(find_spans("a &foo b"), vec![(SpanType::Room, 2..6)]);
|
||||||
|
assert_eq!(find_spans("&&foo&&"), vec![(SpanType::Room, 1..5)]);
|
||||||
|
assert_eq!(find_spans("a &b&c d"), vec![(SpanType::Room, 2..4)]);
|
||||||
|
assert_eq!(
|
||||||
|
find_spans("a &b &c d"),
|
||||||
|
vec![(SpanType::Room, 2..4), (SpanType::Room, 5..7)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn emoji_in_mentions() {
|
||||||
|
assert_eq!(find_spans(" @a:b:c "), vec![(SpanType::Mention, 1..7)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,21 +1,17 @@
|
||||||
// TODO Remove rl2dev-specific code
|
use std::{convert::Infallible, time::Duration};
|
||||||
|
|
||||||
use std::convert::Infallible;
|
use euphoxide::{
|
||||||
use std::time::Duration;
|
api::{
|
||||||
|
Auth, AuthOption, Data, Log, Login, Logout, MessageId, Nick, Send, SendEvent, SendReply,
|
||||||
use euphoxide::api::packet::ParsedPacket;
|
Time, UserId, packet::ParsedPacket,
|
||||||
use euphoxide::api::{
|
},
|
||||||
Auth, AuthOption, Data, Log, Login, Logout, MessageId, Nick, Send, SendEvent, SendReply, Time,
|
bot::instance::{ConnSnapshot, Event, Instance, InstanceConfig},
|
||||||
UserId,
|
conn::{self, ConnTx, Joined},
|
||||||
};
|
};
|
||||||
use euphoxide::bot::instance::{ConnSnapshot, Event, Instance, InstanceConfig};
|
use log::{debug, info, warn};
|
||||||
use euphoxide::conn::{self, ConnTx, Joined};
|
use tokio::{select, sync::oneshot};
|
||||||
use log::{debug, error, info, warn};
|
|
||||||
use tokio::select;
|
|
||||||
use tokio::sync::oneshot;
|
|
||||||
|
|
||||||
use crate::macros::logging_unwrap;
|
use crate::{macros::logging_unwrap, vault::EuphRoomVault};
|
||||||
use crate::vault::EuphRoomVault;
|
|
||||||
|
|
||||||
const LOG_INTERVAL: Duration = Duration::from_secs(10);
|
const LOG_INTERVAL: Duration = Duration::from_secs(10);
|
||||||
|
|
||||||
|
|
@ -73,20 +69,13 @@ impl Room {
|
||||||
where
|
where
|
||||||
F: Fn(Event) + std::marker::Send + Sync + 'static,
|
F: Fn(Event) + std::marker::Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
// &rl2dev's message history is broken and requesting old messages past
|
|
||||||
// a certain point results in errors. Cove should not keep retrying log
|
|
||||||
// requests when hitting that limit, so &rl2dev is always opened in
|
|
||||||
// ephemeral mode.
|
|
||||||
let is_rl2dev = vault.room().domain == "euphoria.io" && vault.room().name == "rl2dev";
|
|
||||||
let ephemeral = vault.vault().vault().ephemeral() || is_rl2dev;
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
vault,
|
ephemeral: vault.vault().vault().ephemeral(),
|
||||||
ephemeral,
|
|
||||||
instance: instance_config.build(on_event),
|
instance: instance_config.build(on_event),
|
||||||
state: State::Disconnected,
|
state: State::Disconnected,
|
||||||
last_msg_id: None,
|
last_msg_id: None,
|
||||||
log_request_canary: None,
|
log_request_canary: None,
|
||||||
|
vault,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -194,14 +183,7 @@ impl Room {
|
||||||
|
|
||||||
debug!("{:?}: requesting logs", vault.room());
|
debug!("{:?}: requesting logs", vault.room());
|
||||||
|
|
||||||
// &rl2dev's message history is broken and requesting old messages past
|
let _ = conn_tx.send(Log { n: 1000, before }).await;
|
||||||
// a certain point results in errors. By reducing the amount of messages
|
|
||||||
// in each log request, we can get closer to this point. Since &rl2dev
|
|
||||||
// is fairly low in activity, this should be fine.
|
|
||||||
let is_rl2dev = vault.room().domain == "euphoria.io" && vault.room().name == "rl2dev";
|
|
||||||
let n = if is_rl2dev { 50 } else { 1000 };
|
|
||||||
|
|
||||||
let _ = conn_tx.send(Log { n, before }).await;
|
|
||||||
// The code handling incoming events and replies also handles
|
// The code handling incoming events and replies also handles
|
||||||
// `LogReply`s, so we don't need to do anything special here.
|
// `LogReply`s, so we don't need to do anything special here.
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,212 +1,18 @@
|
||||||
use std::mem;
|
|
||||||
|
|
||||||
use crossterm::style::Stylize;
|
use crossterm::style::Stylize;
|
||||||
use euphoxide::api::{MessageId, Snowflake, Time};
|
use euphoxide::api::{MessageId, Snowflake, Time, UserId};
|
||||||
use jiff::Timestamp;
|
use jiff::Timestamp;
|
||||||
use toss::{Style, Styled};
|
use toss::{Style, Styled};
|
||||||
|
|
||||||
use crate::store::Msg;
|
use crate::{store::Msg, ui::ChatMsg};
|
||||||
use crate::ui::ChatMsg;
|
|
||||||
|
|
||||||
use super::util;
|
use super::util;
|
||||||
|
|
||||||
fn nick_char(ch: char) -> bool {
|
|
||||||
// Closely following the heim mention regex:
|
|
||||||
// https://github.com/euphoria-io/heim/blob/978c921063e6b06012fc8d16d9fbf1b3a0be1191/client/lib/stores/chat.js#L14-L15
|
|
||||||
// `>` has been experimentally confirmed to delimit mentions as well.
|
|
||||||
match ch {
|
|
||||||
',' | '.' | '!' | '?' | ';' | '&' | '<' | '>' | '\'' | '"' => false,
|
|
||||||
_ => !ch.is_whitespace(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn room_char(ch: char) -> bool {
|
|
||||||
// Basically just \w, see also
|
|
||||||
// https://github.com/euphoria-io/heim/blob/978c921063e6b06012fc8d16d9fbf1b3a0be1191/client/lib/ui/MessageText.js#L66
|
|
||||||
ch.is_ascii_alphanumeric() || ch == '_'
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Span {
|
|
||||||
Nothing,
|
|
||||||
Mention,
|
|
||||||
Room,
|
|
||||||
Emoji,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Highlighter<'a> {
|
|
||||||
content: &'a str,
|
|
||||||
base_style: Style,
|
|
||||||
exact: bool,
|
|
||||||
|
|
||||||
span: Span,
|
|
||||||
span_start: usize,
|
|
||||||
room_or_mention_possible: bool,
|
|
||||||
|
|
||||||
result: Styled,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Highlighter<'a> {
|
|
||||||
/// Does *not* guarantee `self.span_start == idx` after running!
|
|
||||||
fn close_mention(&mut self, idx: usize) {
|
|
||||||
let span_length = idx.saturating_sub(self.span_start);
|
|
||||||
if span_length <= 1 {
|
|
||||||
// We can repurpose the current span
|
|
||||||
self.span = Span::Nothing;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let text = &self.content[self.span_start..idx]; // Includes @
|
|
||||||
self.result = mem::take(&mut self.result).and_then(if self.exact {
|
|
||||||
util::style_nick_exact(text, self.base_style)
|
|
||||||
} else {
|
|
||||||
util::style_nick(text, self.base_style)
|
|
||||||
});
|
|
||||||
|
|
||||||
self.span = Span::Nothing;
|
|
||||||
self.span_start = idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Does *not* guarantee `self.span_start == idx` after running!
|
|
||||||
fn close_room(&mut self, idx: usize) {
|
|
||||||
let span_length = idx.saturating_sub(self.span_start);
|
|
||||||
if span_length <= 1 {
|
|
||||||
// We can repurpose the current span
|
|
||||||
self.span = Span::Nothing;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.result = mem::take(&mut self.result).then(
|
|
||||||
&self.content[self.span_start..idx],
|
|
||||||
self.base_style.blue().bold(),
|
|
||||||
);
|
|
||||||
|
|
||||||
self.span = Span::Nothing;
|
|
||||||
self.span_start = idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warning: `idx` is the index of the closing colon.
|
|
||||||
fn close_emoji(&mut self, idx: usize) {
|
|
||||||
let name = &self.content[self.span_start + 1..idx];
|
|
||||||
if let Some(replace) = util::EMOJI.get(name) {
|
|
||||||
match replace {
|
|
||||||
Some(replace) if !self.exact => {
|
|
||||||
self.result = mem::take(&mut self.result).then(replace, self.base_style);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let text = &self.content[self.span_start..=idx];
|
|
||||||
let style = self.base_style.magenta();
|
|
||||||
self.result = mem::take(&mut self.result).then(text, style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.span = Span::Nothing;
|
|
||||||
self.span_start = idx + 1;
|
|
||||||
} else {
|
|
||||||
self.close_plain(idx);
|
|
||||||
self.span = Span::Emoji;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Guarantees `self.span_start == idx` after running.
|
|
||||||
fn close_plain(&mut self, idx: usize) {
|
|
||||||
if self.span_start == idx {
|
|
||||||
// Span has length 0
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.result =
|
|
||||||
mem::take(&mut self.result).then(&self.content[self.span_start..idx], self.base_style);
|
|
||||||
|
|
||||||
self.span = Span::Nothing;
|
|
||||||
self.span_start = idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn close_span_before_current_char(&mut self, idx: usize, char: char) {
|
|
||||||
match self.span {
|
|
||||||
Span::Mention if !nick_char(char) => self.close_mention(idx),
|
|
||||||
Span::Room if !room_char(char) => self.close_room(idx),
|
|
||||||
Span::Emoji if char == '&' || char == '@' => {
|
|
||||||
self.span = Span::Nothing;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_span_with_current_char(&mut self, idx: usize, char: char) {
|
|
||||||
match self.span {
|
|
||||||
Span::Nothing if char == '@' && self.room_or_mention_possible => {
|
|
||||||
self.close_plain(idx);
|
|
||||||
self.span = Span::Mention;
|
|
||||||
}
|
|
||||||
Span::Nothing if char == '&' && self.room_or_mention_possible => {
|
|
||||||
self.close_plain(idx);
|
|
||||||
self.span = Span::Room;
|
|
||||||
}
|
|
||||||
Span::Nothing if char == ':' => {
|
|
||||||
self.close_plain(idx);
|
|
||||||
self.span = Span::Emoji;
|
|
||||||
}
|
|
||||||
Span::Emoji if char == ':' => self.close_emoji(idx),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn close_final_span(&mut self) {
|
|
||||||
let idx = self.content.len();
|
|
||||||
if self.span_start >= idx {
|
|
||||||
return; // Span has no contents
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.span {
|
|
||||||
Span::Mention => self.close_mention(idx),
|
|
||||||
Span::Room => self.close_room(idx),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.close_plain(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn step(&mut self, idx: usize, char: char) {
|
|
||||||
if self.span_start < idx {
|
|
||||||
self.close_span_before_current_char(idx, char);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.update_span_with_current_char(idx, char);
|
|
||||||
|
|
||||||
// More permissive than the heim web client
|
|
||||||
self.room_or_mention_possible = !char.is_alphanumeric();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn highlight(content: &'a str, base_style: Style, exact: bool) -> Styled {
|
|
||||||
let mut this = Self {
|
|
||||||
content: if exact { content } else { content.trim() },
|
|
||||||
base_style,
|
|
||||||
exact,
|
|
||||||
span: Span::Nothing,
|
|
||||||
span_start: 0,
|
|
||||||
room_or_mention_possible: true,
|
|
||||||
result: Styled::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
for (idx, char) in (if exact { content } else { content.trim() }).char_indices() {
|
|
||||||
this.step(idx, char);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.close_final_span();
|
|
||||||
|
|
||||||
this.result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn highlight_content(content: &str, base_style: Style, exact: bool) -> Styled {
|
|
||||||
Highlighter::highlight(content, base_style, exact)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SmallMessage {
|
pub struct SmallMessage {
|
||||||
pub id: MessageId,
|
pub id: MessageId,
|
||||||
pub parent: Option<MessageId>,
|
pub parent: Option<MessageId>,
|
||||||
pub time: Time,
|
pub time: Time,
|
||||||
|
pub user_id: UserId,
|
||||||
pub nick: String,
|
pub nick: String,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub seen: bool,
|
pub seen: bool,
|
||||||
|
|
@ -222,22 +28,22 @@ fn style_me() -> Style {
|
||||||
|
|
||||||
fn styled_nick(nick: &str) -> Styled {
|
fn styled_nick(nick: &str) -> Styled {
|
||||||
Styled::new_plain("[")
|
Styled::new_plain("[")
|
||||||
.and_then(util::style_nick(nick, Style::new()))
|
.and_then(super::style_nick(nick, Style::new()))
|
||||||
.then_plain("]")
|
.then_plain("]")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn styled_nick_me(nick: &str) -> Styled {
|
fn styled_nick_me(nick: &str) -> Styled {
|
||||||
let style = style_me();
|
let style = style_me();
|
||||||
Styled::new("*", style).and_then(util::style_nick(nick, style))
|
Styled::new("*", style).and_then(super::style_nick(nick, style))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn styled_content(content: &str) -> Styled {
|
fn styled_content(content: &str) -> Styled {
|
||||||
highlight_content(content.trim(), Style::new(), false)
|
super::highlight(content.trim(), Style::new(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn styled_content_me(content: &str) -> Styled {
|
fn styled_content_me(content: &str) -> Styled {
|
||||||
let style = style_me();
|
let style = style_me();
|
||||||
highlight_content(content.trim(), style, false).then("*", style)
|
super::highlight(content.trim(), style, false).then("*", style)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn styled_editor_content(content: &str) -> Styled {
|
fn styled_editor_content(content: &str) -> Styled {
|
||||||
|
|
@ -246,7 +52,7 @@ fn styled_editor_content(content: &str) -> Styled {
|
||||||
} else {
|
} else {
|
||||||
Style::new()
|
Style::new()
|
||||||
};
|
};
|
||||||
highlight_content(content, style, true)
|
super::highlight(content, style, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Msg for SmallMessage {
|
impl Msg for SmallMessage {
|
||||||
|
|
@ -267,6 +73,10 @@ impl Msg for SmallMessage {
|
||||||
fn last_possible_id() -> Self::Id {
|
fn last_possible_id() -> Self::Id {
|
||||||
MessageId(Snowflake::MAX)
|
MessageId(Snowflake::MAX)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn nick_emoji(&self) -> Option<String> {
|
||||||
|
Some(util::user_id_emoji(&self.user_id))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChatMsg for SmallMessage {
|
impl ChatMsg for SmallMessage {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,27 @@
|
||||||
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
hash::{DefaultHasher, Hash, Hasher},
|
||||||
|
sync::LazyLock,
|
||||||
|
};
|
||||||
|
|
||||||
use crossterm::style::{Color, Stylize};
|
use crossterm::style::{Color, Stylize};
|
||||||
use euphoxide::Emoji;
|
use euphoxide::{Emoji, api::UserId};
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use toss::{Style, Styled};
|
use toss::{Style, Styled};
|
||||||
|
|
||||||
pub static EMOJI: Lazy<Emoji> = Lazy::new(Emoji::load);
|
pub static EMOJI: LazyLock<Emoji> = LazyLock::new(Emoji::load);
|
||||||
|
|
||||||
|
pub static EMOJI_LIST: LazyLock<Vec<String>> = LazyLock::new(|| {
|
||||||
|
let mut list = EMOJI
|
||||||
|
.0
|
||||||
|
.values()
|
||||||
|
.flatten()
|
||||||
|
.cloned()
|
||||||
|
.collect::<HashSet<_>>()
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
list.sort_unstable();
|
||||||
|
list
|
||||||
|
});
|
||||||
|
|
||||||
/// Convert HSL to RGB following [this approach from wikipedia][1].
|
/// Convert HSL to RGB following [this approach from wikipedia][1].
|
||||||
///
|
///
|
||||||
|
|
@ -54,3 +72,25 @@ pub fn style_nick(nick: &str, base: Style) -> Styled {
|
||||||
pub fn style_nick_exact(nick: &str, base: Style) -> Styled {
|
pub fn style_nick_exact(nick: &str, base: Style) -> Styled {
|
||||||
Styled::new(nick, nick_style(nick, base))
|
Styled::new(nick, nick_style(nick, base))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn style_mention(mention: &str, base: Style) -> Styled {
|
||||||
|
let nick = mention
|
||||||
|
.strip_prefix('@')
|
||||||
|
.expect("mention must start with @");
|
||||||
|
Styled::new(EMOJI.replace(mention), nick_style(nick, base))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn style_mention_exact(mention: &str, base: Style) -> Styled {
|
||||||
|
let nick = mention
|
||||||
|
.strip_prefix('@')
|
||||||
|
.expect("mention must start with @");
|
||||||
|
Styled::new(mention, nick_style(nick, base))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn user_id_emoji(user_id: &UserId) -> String {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
user_id.0.hash(&mut hasher);
|
||||||
|
let hash = hasher.finish();
|
||||||
|
let emoji = &EMOJI_LIST[hash as usize % EMOJI_LIST.len()];
|
||||||
|
emoji.clone()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
//! Export logs from the vault to plain text files.
|
//! Export logs from the vault to plain text files.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{self, BufWriter, Write},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::vault::{EuphRoomVault, EuphVault, RoomIdentifier};
|
||||||
|
|
||||||
mod json;
|
mod json;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{self, BufWriter, Write};
|
|
||||||
|
|
||||||
use crate::vault::{EuphRoomVault, EuphVault, RoomIdentifier};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
|
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
|
||||||
pub enum Format {
|
pub enum Format {
|
||||||
/// Human-readable tree-structured messages.
|
/// Human-readable tree-structured messages.
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,7 @@ use std::io::Write;
|
||||||
use euphoxide::api::MessageId;
|
use euphoxide::api::MessageId;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
use crate::euph::SmallMessage;
|
use crate::{euph::SmallMessage, store::Tree, vault::EuphRoomVault};
|
||||||
use crate::store::Tree;
|
|
||||||
use crate::vault::EuphRoomVault;
|
|
||||||
|
|
||||||
const TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S";
|
const TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S";
|
||||||
const TIME_EMPTY: &str = " ";
|
const TIME_EMPTY: &str = " ";
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
use std::convert::Infallible;
|
use std::{convert::Infallible, sync::Arc, vec};
|
||||||
use std::sync::Arc;
|
|
||||||
use std::vec;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use crossterm::style::Stylize;
|
use crossterm::style::Stylize;
|
||||||
|
|
@ -10,8 +8,10 @@ use parking_lot::Mutex;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use toss::{Style, Styled};
|
use toss::{Style, Styled};
|
||||||
|
|
||||||
use crate::store::{Msg, MsgStore, Path, Tree};
|
use crate::{
|
||||||
use crate::ui::ChatMsg;
|
store::{Msg, MsgStore, Path, Tree},
|
||||||
|
ui::ChatMsg,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LogMsg {
|
pub struct LogMsg {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,23 @@
|
||||||
// TODO Remove unnecessary Debug impls and compare compile times
|
// TODO Remove unnecessary Debug impls and compare compile times
|
||||||
// TODO Invoke external notification command?
|
// TODO Invoke external notification command?
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use clap::Parser;
|
||||||
|
use cove_config::{Config, doc::Document};
|
||||||
|
use directories::{BaseDirs, ProjectDirs};
|
||||||
|
use log::info;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use toss::Terminal;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
logger::Logger,
|
||||||
|
ui::Ui,
|
||||||
|
vault::Vault,
|
||||||
|
version::{NAME, VERSION},
|
||||||
|
};
|
||||||
|
|
||||||
mod euph;
|
mod euph;
|
||||||
mod export;
|
mod export;
|
||||||
mod logger;
|
mod logger;
|
||||||
|
|
@ -11,22 +28,6 @@ mod util;
|
||||||
mod vault;
|
mod vault;
|
||||||
mod version;
|
mod version;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use anyhow::Context;
|
|
||||||
use clap::Parser;
|
|
||||||
use cove_config::doc::Document;
|
|
||||||
use cove_config::Config;
|
|
||||||
use directories::{BaseDirs, ProjectDirs};
|
|
||||||
use log::info;
|
|
||||||
use tokio::sync::mpsc;
|
|
||||||
use toss::Terminal;
|
|
||||||
|
|
||||||
use crate::logger::Logger;
|
|
||||||
use crate::ui::Ui;
|
|
||||||
use crate::vault::Vault;
|
|
||||||
use crate::version::{NAME, VERSION};
|
|
||||||
|
|
||||||
#[derive(Debug, clap::Parser)]
|
#[derive(Debug, clap::Parser)]
|
||||||
enum Command {
|
enum Command {
|
||||||
/// Run the client interactively (default).
|
/// Run the client interactively (default).
|
||||||
|
|
@ -45,6 +46,12 @@ enum Command {
|
||||||
HelpConfig,
|
HelpConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
|
||||||
|
enum WidthEstimationMethod {
|
||||||
|
Legacy,
|
||||||
|
Unicode,
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Command {
|
impl Default for Command {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Run
|
Self::Run
|
||||||
|
|
@ -78,6 +85,11 @@ struct Args {
|
||||||
#[arg(long, short)]
|
#[arg(long, short)]
|
||||||
offline: bool,
|
offline: bool,
|
||||||
|
|
||||||
|
/// Method for estimating the width of characters as displayed by the
|
||||||
|
/// terminal emulator.
|
||||||
|
#[arg(long, short)]
|
||||||
|
width_estimation_method: Option<WidthEstimationMethod>,
|
||||||
|
|
||||||
/// Measure the width of characters as displayed by the terminal emulator
|
/// Measure the width of characters as displayed by the terminal emulator
|
||||||
/// instead of guessing the width.
|
/// instead of guessing the width.
|
||||||
#[arg(long, short)]
|
#[arg(long, short)]
|
||||||
|
|
@ -113,6 +125,12 @@ fn update_config_with_args(config: &mut Config, args: &Args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
config.ephemeral |= args.ephemeral;
|
config.ephemeral |= args.ephemeral;
|
||||||
|
if let Some(method) = args.width_estimation_method {
|
||||||
|
config.width_estimation_method = match method {
|
||||||
|
WidthEstimationMethod::Legacy => cove_config::WidthEstimationMethod::Legacy,
|
||||||
|
WidthEstimationMethod::Unicode => cove_config::WidthEstimationMethod::Unicode,
|
||||||
|
}
|
||||||
|
}
|
||||||
config.measure_widths |= args.measure_widths;
|
config.measure_widths |= args.measure_widths;
|
||||||
config.offline |= args.offline;
|
config.offline |= args.offline;
|
||||||
}
|
}
|
||||||
|
|
@ -181,6 +199,10 @@ async fn run(
|
||||||
|
|
||||||
let mut terminal = Terminal::new()?;
|
let mut terminal = Terminal::new()?;
|
||||||
terminal.set_measuring(config.measure_widths);
|
terminal.set_measuring(config.measure_widths);
|
||||||
|
terminal.set_width_estimation_method(match config.width_estimation_method {
|
||||||
|
cove_config::WidthEstimationMethod::Legacy => toss::WidthEstimationMethod::Legacy,
|
||||||
|
cove_config::WidthEstimationMethod::Unicode => toss::WidthEstimationMethod::Unicode,
|
||||||
|
});
|
||||||
Ui::run(config, tz, &mut terminal, vault.clone(), logger, logger_rx).await?;
|
Ui::run(config, tz, &mut terminal, vault.clone(), logger, logger_rx).await?;
|
||||||
drop(terminal);
|
drop(terminal);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, fmt::Debug, hash::Hash, vec};
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::hash::Hash;
|
|
||||||
use std::vec;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
|
@ -11,6 +8,10 @@ pub trait Msg {
|
||||||
fn parent(&self) -> Option<Self::Id>;
|
fn parent(&self) -> Option<Self::Id>;
|
||||||
fn seen(&self) -> bool;
|
fn seen(&self) -> bool;
|
||||||
|
|
||||||
|
fn nick_emoji(&self) -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn last_possible_id() -> Self::Id;
|
fn last_possible_id() -> Self::Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,30 @@
|
||||||
|
use std::{
|
||||||
|
convert::Infallible,
|
||||||
|
io,
|
||||||
|
sync::{Arc, Weak},
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
use cove_config::Config;
|
||||||
|
use cove_input::InputEvent;
|
||||||
|
use jiff::tz::TimeZone;
|
||||||
|
use parking_lot::FairMutex;
|
||||||
|
use tokio::{
|
||||||
|
sync::mpsc::{self, UnboundedReceiver, UnboundedSender, error::TryRecvError},
|
||||||
|
task,
|
||||||
|
};
|
||||||
|
use toss::{Terminal, WidgetExt, widgets::BoxedAsync};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
logger::{LogMsg, Logger},
|
||||||
|
macros::logging_unwrap,
|
||||||
|
util::InfallibleExt,
|
||||||
|
vault::Vault,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub use self::chat::ChatMsg;
|
||||||
|
use self::{chat::ChatState, rooms::Rooms, widgets::ListState};
|
||||||
|
|
||||||
mod chat;
|
mod chat;
|
||||||
mod euph;
|
mod euph;
|
||||||
mod key_bindings;
|
mod key_bindings;
|
||||||
|
|
@ -5,31 +32,6 @@ mod rooms;
|
||||||
mod util;
|
mod util;
|
||||||
mod widgets;
|
mod widgets;
|
||||||
|
|
||||||
use std::convert::Infallible;
|
|
||||||
use std::io;
|
|
||||||
use std::sync::{Arc, Weak};
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
|
|
||||||
use cove_config::Config;
|
|
||||||
use cove_input::InputEvent;
|
|
||||||
use jiff::tz::TimeZone;
|
|
||||||
use parking_lot::FairMutex;
|
|
||||||
use tokio::sync::mpsc::error::TryRecvError;
|
|
||||||
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
|
|
||||||
use tokio::task;
|
|
||||||
use toss::widgets::BoxedAsync;
|
|
||||||
use toss::{Terminal, WidgetExt};
|
|
||||||
|
|
||||||
use crate::logger::{LogMsg, Logger};
|
|
||||||
use crate::macros::logging_unwrap;
|
|
||||||
use crate::util::InfallibleExt;
|
|
||||||
use crate::vault::Vault;
|
|
||||||
|
|
||||||
pub use self::chat::ChatMsg;
|
|
||||||
use self::chat::ChatState;
|
|
||||||
use self::rooms::Rooms;
|
|
||||||
use self::widgets::ListState;
|
|
||||||
|
|
||||||
/// Time to spend batch processing events before redrawing the screen.
|
/// Time to spend batch processing events before redrawing the screen.
|
||||||
const EVENT_PROCESSING_TIME: Duration = Duration::from_millis(1000 / 15); // 15 fps
|
const EVENT_PROCESSING_TIME: Duration = Duration::from_millis(1000 / 15); // 15 fps
|
||||||
|
|
||||||
|
|
@ -48,6 +50,7 @@ impl From<Infallible> for UiError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::large_enum_variant)]
|
||||||
pub enum UiEvent {
|
pub enum UiEvent {
|
||||||
GraphemeWidthsChanged,
|
GraphemeWidthsChanged,
|
||||||
LogChanged,
|
LogChanged,
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,26 @@
|
||||||
|
use cove_config::Keys;
|
||||||
|
use cove_input::InputEvent;
|
||||||
|
use jiff::{Timestamp, tz::TimeZone};
|
||||||
|
use toss::{
|
||||||
|
Styled, WidgetExt,
|
||||||
|
widgets::{BoxedAsync, EditorState},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
store::{Msg, MsgStore},
|
||||||
|
util,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::UiError;
|
||||||
|
|
||||||
|
use self::{cursor::Cursor, tree::TreeViewState};
|
||||||
|
|
||||||
mod blocks;
|
mod blocks;
|
||||||
mod cursor;
|
mod cursor;
|
||||||
mod renderer;
|
mod renderer;
|
||||||
mod tree;
|
mod tree;
|
||||||
mod widgets;
|
mod widgets;
|
||||||
|
|
||||||
use cove_config::Keys;
|
|
||||||
use cove_input::InputEvent;
|
|
||||||
use jiff::tz::TimeZone;
|
|
||||||
use jiff::Timestamp;
|
|
||||||
use toss::widgets::{BoxedAsync, EditorState};
|
|
||||||
use toss::{Styled, WidgetExt};
|
|
||||||
|
|
||||||
use crate::store::{Msg, MsgStore};
|
|
||||||
use crate::util;
|
|
||||||
|
|
||||||
use self::cursor::Cursor;
|
|
||||||
use self::tree::TreeViewState;
|
|
||||||
|
|
||||||
use super::UiError;
|
|
||||||
|
|
||||||
pub trait ChatMsg {
|
pub trait ChatMsg {
|
||||||
fn time(&self) -> Option<Timestamp>;
|
fn time(&self) -> Option<Timestamp>;
|
||||||
fn styled(&self) -> (Styled, Styled);
|
fn styled(&self) -> (Styled, Styled);
|
||||||
|
|
@ -35,6 +37,7 @@ pub struct ChatState<M: Msg, S: MsgStore<M>> {
|
||||||
|
|
||||||
cursor: Cursor<M::Id>,
|
cursor: Cursor<M::Id>,
|
||||||
editor: EditorState,
|
editor: EditorState,
|
||||||
|
nick_emoji: bool,
|
||||||
caesar: i8,
|
caesar: i8,
|
||||||
|
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
|
|
@ -46,6 +49,7 @@ impl<M: Msg, S: MsgStore<M> + Clone> ChatState<M, S> {
|
||||||
Self {
|
Self {
|
||||||
cursor: Cursor::Bottom,
|
cursor: Cursor::Bottom,
|
||||||
editor: EditorState::new(),
|
editor: EditorState::new(),
|
||||||
|
nick_emoji: false,
|
||||||
caesar: 0,
|
caesar: 0,
|
||||||
|
|
||||||
mode: Mode::Tree,
|
mode: Mode::Tree,
|
||||||
|
|
@ -54,6 +58,10 @@ impl<M: Msg, S: MsgStore<M> + Clone> ChatState<M, S> {
|
||||||
store,
|
store,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn nick_emoji(&self) -> bool {
|
||||||
|
self.nick_emoji
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: Msg, S: MsgStore<M>> ChatState<M, S> {
|
impl<M: Msg, S: MsgStore<M>> ChatState<M, S> {
|
||||||
|
|
@ -77,6 +85,7 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> {
|
||||||
&mut self.editor,
|
&mut self.editor,
|
||||||
nick,
|
nick,
|
||||||
focused,
|
focused,
|
||||||
|
self.nick_emoji,
|
||||||
self.caesar,
|
self.caesar,
|
||||||
)
|
)
|
||||||
.boxed_async(),
|
.boxed_async(),
|
||||||
|
|
@ -115,6 +124,11 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> {
|
||||||
Reaction::Composed { parent, content }
|
Reaction::Composed { parent, content }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reaction::NotHandled if event.matches(&keys.tree.action.toggle_nick_emoji) => {
|
||||||
|
self.nick_emoji = !self.nick_emoji;
|
||||||
|
Reaction::Handled
|
||||||
|
}
|
||||||
|
|
||||||
Reaction::NotHandled if event.matches(&keys.tree.action.increase_caesar) => {
|
Reaction::NotHandled if event.matches(&keys.tree.action.increase_caesar) => {
|
||||||
self.caesar = (self.caesar + 1).rem_euclid(26);
|
self.caesar = (self.caesar + 1).rem_euclid(26);
|
||||||
Reaction::Handled
|
Reaction::Handled
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Common rendering logic.
|
//! Common rendering logic.
|
||||||
|
|
||||||
use std::collections::{vec_deque, VecDeque};
|
use std::collections::{VecDeque, vec_deque};
|
||||||
|
|
||||||
use toss::widgets::Predrawn;
|
use toss::widgets::Predrawn;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
//! Common cursor movement logic.
|
//! Common cursor movement logic.
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::{collections::HashSet, hash::Hash};
|
||||||
use std::hash::Hash;
|
|
||||||
|
|
||||||
use crate::store::{Msg, MsgStore, Tree};
|
use crate::store::{Msg, MsgStore, Tree};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,27 @@
|
||||||
|
|
||||||
// TODO Focusing on sub-trees
|
// TODO Focusing on sub-trees
|
||||||
|
|
||||||
mod renderer;
|
|
||||||
mod scroll;
|
|
||||||
mod widgets;
|
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use cove_config::Keys;
|
use cove_config::Keys;
|
||||||
use cove_input::InputEvent;
|
use cove_input::InputEvent;
|
||||||
use jiff::tz::TimeZone;
|
use jiff::tz::TimeZone;
|
||||||
use toss::widgets::EditorState;
|
use toss::{AsyncWidget, Frame, Pos, Size, WidgetExt, WidthDb, widgets::EditorState};
|
||||||
use toss::{AsyncWidget, Frame, Pos, Size, WidgetExt, WidthDb};
|
|
||||||
|
|
||||||
use crate::store::{Msg, MsgStore};
|
use crate::{
|
||||||
use crate::ui::{util, ChatMsg, UiError};
|
store::{Msg, MsgStore},
|
||||||
use crate::util::InfallibleExt;
|
ui::{UiError, util},
|
||||||
|
util::InfallibleExt,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{ChatMsg, Reaction, cursor::Cursor};
|
||||||
|
|
||||||
use self::renderer::{TreeContext, TreeRenderer};
|
use self::renderer::{TreeContext, TreeRenderer};
|
||||||
|
|
||||||
use super::cursor::Cursor;
|
mod renderer;
|
||||||
use super::Reaction;
|
mod scroll;
|
||||||
|
mod widgets;
|
||||||
|
|
||||||
pub struct TreeViewState<M: Msg, S: MsgStore<M>> {
|
pub struct TreeViewState<M: Msg, S: MsgStore<M>> {
|
||||||
store: S,
|
store: S,
|
||||||
|
|
@ -389,6 +389,7 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> {
|
||||||
editor: &'a mut EditorState,
|
editor: &'a mut EditorState,
|
||||||
nick: String,
|
nick: String,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
|
nick_emoji: bool,
|
||||||
caesar: i8,
|
caesar: i8,
|
||||||
) -> TreeView<'a, M, S> {
|
) -> TreeView<'a, M, S> {
|
||||||
TreeView {
|
TreeView {
|
||||||
|
|
@ -397,6 +398,7 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> {
|
||||||
editor,
|
editor,
|
||||||
nick,
|
nick,
|
||||||
focused,
|
focused,
|
||||||
|
nick_emoji,
|
||||||
caesar,
|
caesar,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -410,6 +412,8 @@ pub struct TreeView<'a, M: Msg, S: MsgStore<M>> {
|
||||||
|
|
||||||
nick: String,
|
nick: String,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
|
|
||||||
|
nick_emoji: bool,
|
||||||
caesar: i8,
|
caesar: i8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -438,6 +442,7 @@ where
|
||||||
size,
|
size,
|
||||||
nick: self.nick.clone(),
|
nick: self.nick.clone(),
|
||||||
focused: self.focused,
|
focused: self.focused,
|
||||||
|
nick_emoji: self.nick_emoji,
|
||||||
caesar: self.caesar,
|
caesar: self.caesar,
|
||||||
last_cursor: self.state.last_cursor.clone(),
|
last_cursor: self.state.last_cursor.clone(),
|
||||||
last_cursor_top: self.state.last_cursor_top,
|
last_cursor_top: self.state.last_cursor_top,
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,26 @@
|
||||||
//! A [`Renderer`] for message trees.
|
//! A [`Renderer`] for message trees.
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::{collections::HashSet, convert::Infallible};
|
||||||
use std::convert::Infallible;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use jiff::tz::TimeZone;
|
use jiff::tz::TimeZone;
|
||||||
use toss::widgets::{EditorState, Empty, Predrawn, Resize};
|
use toss::{
|
||||||
use toss::{Size, Widget, WidthDb};
|
Size, Widget, WidthDb,
|
||||||
|
widgets::{EditorState, Empty, Predrawn, Resize},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::store::{Msg, MsgStore, Tree};
|
use crate::{
|
||||||
use crate::ui::chat::blocks::{Block, Blocks, Range};
|
store::{Msg, MsgStore, Tree},
|
||||||
use crate::ui::chat::cursor::Cursor;
|
ui::{
|
||||||
use crate::ui::chat::renderer::{self, overlaps, Renderer};
|
ChatMsg,
|
||||||
use crate::ui::ChatMsg;
|
chat::{
|
||||||
use crate::util::InfallibleExt;
|
blocks::{Block, Blocks, Range},
|
||||||
|
cursor::Cursor,
|
||||||
|
renderer::{self, Renderer, overlaps},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
util::InfallibleExt,
|
||||||
|
};
|
||||||
|
|
||||||
use super::widgets;
|
use super::widgets;
|
||||||
|
|
||||||
|
|
@ -73,6 +80,7 @@ pub struct TreeContext<Id> {
|
||||||
pub size: Size,
|
pub size: Size,
|
||||||
pub nick: String,
|
pub nick: String,
|
||||||
pub focused: bool,
|
pub focused: bool,
|
||||||
|
pub nick_emoji: bool,
|
||||||
pub caesar: i8,
|
pub caesar: i8,
|
||||||
pub last_cursor: Cursor<Id>,
|
pub last_cursor: Cursor<Id>,
|
||||||
pub last_cursor_top: i32,
|
pub last_cursor_top: i32,
|
||||||
|
|
@ -200,6 +208,7 @@ where
|
||||||
self.tz.clone(),
|
self.tz.clone(),
|
||||||
indent,
|
indent,
|
||||||
msg,
|
msg,
|
||||||
|
self.context.nick_emoji,
|
||||||
self.context.caesar,
|
self.context.caesar,
|
||||||
folded_info,
|
folded_info,
|
||||||
);
|
);
|
||||||
|
|
@ -437,7 +446,7 @@ where
|
||||||
|
|
||||||
pub fn into_visible_blocks(
|
pub fn into_visible_blocks(
|
||||||
self,
|
self,
|
||||||
) -> impl Iterator<Item = (Range<i32>, Block<TreeBlockId<M::Id>>)> {
|
) -> impl Iterator<Item = (Range<i32>, Block<TreeBlockId<M::Id>>)> + use<M, S> {
|
||||||
let area = renderer::visible_area(&self);
|
let area = renderer::visible_area(&self);
|
||||||
self.blocks
|
self.blocks
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
use toss::widgets::EditorState;
|
use toss::{WidthDb, widgets::EditorState};
|
||||||
use toss::WidthDb;
|
|
||||||
|
|
||||||
use crate::store::{Msg, MsgStore};
|
use crate::{
|
||||||
use crate::ui::chat::cursor::Cursor;
|
store::{Msg, MsgStore},
|
||||||
use crate::ui::ChatMsg;
|
ui::{ChatMsg, chat::cursor::Cursor},
|
||||||
|
};
|
||||||
|
|
||||||
use super::renderer::{TreeContext, TreeRenderer};
|
use super::{
|
||||||
use super::TreeViewState;
|
TreeViewState,
|
||||||
|
renderer::{TreeContext, TreeRenderer},
|
||||||
|
};
|
||||||
|
|
||||||
impl<M, S> TreeViewState<M, S>
|
impl<M, S> TreeViewState<M, S>
|
||||||
where
|
where
|
||||||
|
|
@ -20,6 +22,7 @@ where
|
||||||
size: self.last_size,
|
size: self.last_size,
|
||||||
nick: self.last_nick.clone(),
|
nick: self.last_nick.clone(),
|
||||||
focused: true,
|
focused: true,
|
||||||
|
nick_emoji: false,
|
||||||
caesar: 0,
|
caesar: 0,
|
||||||
last_cursor: self.last_cursor.clone(),
|
last_cursor: self.last_cursor.clone(),
|
||||||
last_cursor_top: self.last_cursor_top,
|
last_cursor_top: self.last_cursor_top,
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,19 @@ use std::convert::Infallible;
|
||||||
|
|
||||||
use crossterm::style::Stylize;
|
use crossterm::style::Stylize;
|
||||||
use jiff::tz::TimeZone;
|
use jiff::tz::TimeZone;
|
||||||
use toss::widgets::{Boxed, EditorState, Join2, Join4, Join5, Text};
|
use toss::{
|
||||||
use toss::{Style, Styled, WidgetExt};
|
Style, Styled, WidgetExt,
|
||||||
|
widgets::{Boxed, EditorState, Join2, Join4, Join5, Text},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::store::Msg;
|
use crate::{
|
||||||
use crate::ui::chat::widgets::{Indent, Seen, Time};
|
store::Msg,
|
||||||
use crate::ui::ChatMsg;
|
ui::{
|
||||||
use crate::util;
|
ChatMsg,
|
||||||
|
chat::widgets::{Indent, Seen, Time},
|
||||||
|
},
|
||||||
|
util,
|
||||||
|
};
|
||||||
|
|
||||||
pub const PLACEHOLDER: &str = "[...]";
|
pub const PLACEHOLDER: &str = "[...]";
|
||||||
|
|
||||||
|
|
@ -53,10 +59,17 @@ pub fn msg<M: Msg + ChatMsg>(
|
||||||
tz: TimeZone,
|
tz: TimeZone,
|
||||||
indent: usize,
|
indent: usize,
|
||||||
msg: &M,
|
msg: &M,
|
||||||
|
nick_emoji: bool,
|
||||||
caesar: i8,
|
caesar: i8,
|
||||||
folded_info: Option<usize>,
|
folded_info: Option<usize>,
|
||||||
) -> Boxed<'static, Infallible> {
|
) -> Boxed<'static, Infallible> {
|
||||||
let (nick, mut content) = msg.styled();
|
let (mut nick, mut content) = msg.styled();
|
||||||
|
|
||||||
|
if nick_emoji {
|
||||||
|
if let Some(emoji) = msg.nick_emoji() {
|
||||||
|
nick = nick.then_plain("(").then_plain(emoji).then_plain(")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if caesar != 0 {
|
if caesar != 0 {
|
||||||
// Apply caesar in inverse because we're decoding
|
// Apply caesar in inverse because we're decoding
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@ use std::convert::Infallible;
|
||||||
|
|
||||||
use crossterm::style::Stylize;
|
use crossterm::style::Stylize;
|
||||||
use jiff::Zoned;
|
use jiff::Zoned;
|
||||||
use toss::widgets::{Boxed, Empty, Text};
|
use toss::{
|
||||||
use toss::{Frame, Pos, Size, Style, Widget, WidgetExt, WidthDb};
|
Frame, Pos, Size, Style, Widget, WidgetExt, WidthDb,
|
||||||
|
widgets::{Boxed, Empty, Text},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::util::InfallibleExt;
|
use crate::util::InfallibleExt;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
use cove_config::Keys;
|
use cove_config::Keys;
|
||||||
use cove_input::InputEvent;
|
use cove_input::InputEvent;
|
||||||
use crossterm::style::Stylize;
|
use crossterm::style::Stylize;
|
||||||
use euphoxide::api::PersonalAccountView;
|
use euphoxide::{api::PersonalAccountView, conn};
|
||||||
use euphoxide::conn;
|
use toss::{
|
||||||
use toss::widgets::{EditorState, Empty, Join3, Join4, Join5, Text};
|
Style, Widget, WidgetExt,
|
||||||
use toss::{Style, Widget, WidgetExt};
|
widgets::{EditorState, Empty, Join3, Join4, Join5, Text},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::euph::{self, Room};
|
use crate::{
|
||||||
use crate::ui::widgets::Popup;
|
euph::{self, Room},
|
||||||
use crate::ui::{util, UiError};
|
ui::{UiError, util, widgets::Popup},
|
||||||
|
};
|
||||||
|
|
||||||
use super::popup::PopupResult;
|
use super::popup::PopupResult;
|
||||||
|
|
||||||
|
|
@ -33,7 +35,7 @@ impl LoggedOut {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn widget(&mut self) -> impl Widget<UiError> + '_ {
|
fn widget(&mut self) -> impl Widget<UiError> {
|
||||||
let bold = Style::new().bold();
|
let bold = Style::new().bold();
|
||||||
Join4::vertical(
|
Join4::vertical(
|
||||||
Text::new(("Not logged in", bold.yellow())).segment(),
|
Text::new(("Not logged in", bold.yellow())).segment(),
|
||||||
|
|
@ -66,7 +68,7 @@ impl LoggedOut {
|
||||||
pub struct LoggedIn(PersonalAccountView);
|
pub struct LoggedIn(PersonalAccountView);
|
||||||
|
|
||||||
impl LoggedIn {
|
impl LoggedIn {
|
||||||
fn widget(&self) -> impl Widget<UiError> {
|
fn widget(&self) -> impl Widget<UiError> + use<> {
|
||||||
let bold = Style::new().bold();
|
let bold = Style::new().bold();
|
||||||
Join5::vertical(
|
Join5::vertical(
|
||||||
Text::new(("Logged in", bold.green())).segment(),
|
Text::new(("Logged in", bold.green())).segment(),
|
||||||
|
|
@ -109,7 +111,7 @@ impl AccountUiState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn widget(&mut self) -> impl Widget<UiError> + '_ {
|
pub fn widget(&mut self) -> impl Widget<UiError> {
|
||||||
let inner = match self {
|
let inner = match self {
|
||||||
Self::LoggedOut(logged_out) => logged_out.widget().first2(),
|
Self::LoggedOut(logged_out) => logged_out.widget().first2(),
|
||||||
Self::LoggedIn(logged_in) => logged_in.widget().second2(),
|
Self::LoggedIn(logged_in) => logged_in.widget().second2(),
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use cove_config::Keys;
|
use cove_config::Keys;
|
||||||
use cove_input::InputEvent;
|
use cove_input::InputEvent;
|
||||||
use toss::widgets::EditorState;
|
use toss::{Widget, widgets::EditorState};
|
||||||
use toss::Widget;
|
|
||||||
|
|
||||||
use crate::euph::Room;
|
use crate::{
|
||||||
use crate::ui::widgets::Popup;
|
euph::Room,
|
||||||
use crate::ui::{util, UiError};
|
ui::{UiError, util, widgets::Popup},
|
||||||
|
};
|
||||||
|
|
||||||
use super::popup::PopupResult;
|
use super::popup::PopupResult;
|
||||||
|
|
||||||
|
|
@ -13,7 +13,7 @@ pub fn new() -> EditorState {
|
||||||
EditorState::new()
|
EditorState::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn widget(editor: &mut EditorState) -> impl Widget<UiError> + '_ {
|
pub fn widget(editor: &mut EditorState) -> impl Widget<UiError> {
|
||||||
Popup::new(
|
Popup::new(
|
||||||
editor.widget().with_hidden_default_placeholder(),
|
editor.widget().with_hidden_default_placeholder(),
|
||||||
"Enter password",
|
"Enter password",
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
use cove_config::Keys;
|
use cove_config::Keys;
|
||||||
use cove_input::InputEvent;
|
use cove_input::InputEvent;
|
||||||
use crossterm::style::Stylize;
|
use crossterm::style::Stylize;
|
||||||
use euphoxide::api::{Message, NickEvent, SessionView};
|
use euphoxide::{
|
||||||
use euphoxide::conn::SessionInfo;
|
api::{Message, NickEvent, SessionView},
|
||||||
use toss::widgets::Text;
|
conn::SessionInfo,
|
||||||
use toss::{Style, Styled, Widget};
|
};
|
||||||
|
use toss::{Style, Styled, Widget, widgets::Text};
|
||||||
|
|
||||||
use crate::ui::widgets::Popup;
|
use crate::ui::{UiError, widgets::Popup};
|
||||||
use crate::ui::UiError;
|
|
||||||
|
|
||||||
use super::popup::PopupResult;
|
use super::popup::PopupResult;
|
||||||
|
|
||||||
|
|
@ -91,7 +91,7 @@ fn message_lines(mut text: Styled, msg: &Message) -> Styled {
|
||||||
text
|
text
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_widget(session: &SessionInfo) -> impl Widget<UiError> {
|
pub fn session_widget(session: &SessionInfo) -> impl Widget<UiError> + use<> {
|
||||||
let heading_style = Style::new().bold();
|
let heading_style = Style::new().bold();
|
||||||
|
|
||||||
let text = match session {
|
let text = match session {
|
||||||
|
|
@ -108,7 +108,7 @@ pub fn session_widget(session: &SessionInfo) -> impl Widget<UiError> {
|
||||||
Popup::new(Text::new(text), "Inspect session")
|
Popup::new(Text::new(text), "Inspect session")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn message_widget(msg: &Message) -> impl Widget<UiError> {
|
pub fn message_widget(msg: &Message) -> impl Widget<UiError> + use<> {
|
||||||
let heading_style = Style::new().bold();
|
let heading_style = Style::new().bold();
|
||||||
|
|
||||||
let mut text = Styled::new("Message", heading_style).then_plain("\n");
|
let mut text = Styled::new("Message", heading_style).then_plain("\n");
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,31 @@
|
||||||
use cove_config::{Config, Keys};
|
use cove_config::{Config, Keys};
|
||||||
use cove_input::InputEvent;
|
use cove_input::InputEvent;
|
||||||
use crossterm::event::KeyCode;
|
use crossterm::{event::KeyCode, style::Stylize};
|
||||||
use crossterm::style::Stylize;
|
|
||||||
use linkify::{LinkFinder, LinkKind};
|
use linkify::{LinkFinder, LinkKind};
|
||||||
use toss::widgets::{Join2, Text};
|
use toss::{
|
||||||
use toss::{Style, Styled, Widget, WidgetExt};
|
Style, Styled, Widget, WidgetExt,
|
||||||
|
widgets::{Join2, Text},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::ui::widgets::{ListBuilder, ListState, Popup};
|
use crate::{
|
||||||
use crate::ui::{key_bindings, util, UiError};
|
euph::{self, SpanType},
|
||||||
|
ui::{
|
||||||
|
UiError, key_bindings, util,
|
||||||
|
widgets::{ListBuilder, ListState, Popup},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
use super::popup::PopupResult;
|
use super::popup::PopupResult;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
enum Link {
|
||||||
|
Url(String),
|
||||||
|
Room(String),
|
||||||
|
}
|
||||||
|
|
||||||
pub struct LinksState {
|
pub struct LinksState {
|
||||||
config: &'static Config,
|
config: &'static Config,
|
||||||
links: Vec<String>,
|
links: Vec<Link>,
|
||||||
list: ListState<usize>,
|
list: ListState<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,12 +33,34 @@ const NUMBER_KEYS: [char; 10] = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0
|
||||||
|
|
||||||
impl LinksState {
|
impl LinksState {
|
||||||
pub fn new(config: &'static Config, content: &str) -> Self {
|
pub fn new(config: &'static Config, content: &str) -> Self {
|
||||||
let links = LinkFinder::new()
|
let mut links = vec![];
|
||||||
|
|
||||||
|
// Collect URL-like links
|
||||||
|
for link in LinkFinder::new()
|
||||||
.url_must_have_scheme(false)
|
.url_must_have_scheme(false)
|
||||||
.kinds(&[LinkKind::Url])
|
.kinds(&[LinkKind::Url])
|
||||||
.links(content)
|
.links(content)
|
||||||
.map(|l| l.as_str().to_string())
|
{
|
||||||
.collect();
|
links.push((
|
||||||
|
link.start(),
|
||||||
|
link.end(),
|
||||||
|
Link::Url(link.as_str().to_string()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect room links
|
||||||
|
for (span, range) in euph::find_spans(content) {
|
||||||
|
if span == SpanType::Room {
|
||||||
|
let name = &content[range.start + 1..range.end];
|
||||||
|
links.push((range.start, range.end, Link::Room(name.to_string())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
links.sort();
|
||||||
|
let links = links
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_, _, link)| link)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
config,
|
config,
|
||||||
|
|
@ -35,7 +69,7 @@ impl LinksState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn widget(&mut self) -> impl Widget<UiError> + '_ {
|
pub fn widget(&mut self) -> impl Widget<UiError> {
|
||||||
let style_selected = Style::new().black().on_white();
|
let style_selected = Style::new().black().on_white();
|
||||||
|
|
||||||
let mut list_builder = ListBuilder::new();
|
let mut list_builder = ListBuilder::new();
|
||||||
|
|
@ -46,29 +80,29 @@ impl LinksState {
|
||||||
|
|
||||||
for (id, link) in self.links.iter().enumerate() {
|
for (id, link) in self.links.iter().enumerate() {
|
||||||
let link = link.clone();
|
let link = link.clone();
|
||||||
if let Some(&number_key) = NUMBER_KEYS.get(id) {
|
|
||||||
list_builder.add_sel(id, move |selected| {
|
list_builder.add_sel(id, move |selected| {
|
||||||
let text = if selected {
|
let mut text = Styled::default();
|
||||||
Styled::new(format!("[{number_key}]"), style_selected.bold())
|
|
||||||
.then(" ", style_selected)
|
// Number key indicator
|
||||||
.then(link, style_selected)
|
text = match NUMBER_KEYS.get(id) {
|
||||||
} else {
|
None if selected => text.then(" ", style_selected),
|
||||||
Styled::new(format!("[{number_key}]"), Style::new().dark_grey().bold())
|
None => text.then_plain(" "),
|
||||||
.then_plain(" ")
|
Some(key) if selected => text.then(format!("[{key}] "), style_selected.bold()),
|
||||||
.then_plain(link)
|
Some(key) => text.then(format!("[{key}] "), Style::new().dark_grey().bold()),
|
||||||
};
|
};
|
||||||
Text::new(text)
|
|
||||||
});
|
// The link itself
|
||||||
} else {
|
text = match link {
|
||||||
list_builder.add_sel(id, move |selected| {
|
Link::Url(url) if selected => text.then(url, style_selected),
|
||||||
let text = if selected {
|
Link::Url(url) => text.then_plain(url),
|
||||||
Styled::new(format!(" {link}"), style_selected)
|
Link::Room(name) if selected => {
|
||||||
} else {
|
text.then(format!("&{name}"), style_selected.bold())
|
||||||
Styled::new_plain(format!(" {link}"))
|
|
||||||
};
|
|
||||||
Text::new(text)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
Link::Room(name) => text.then(format!("&{name}"), Style::new().blue().bold()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Text::new(text).with_wrap(false)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let hint_style = Style::new().grey().italic();
|
let hint_style = Style::new().grey().italic();
|
||||||
|
|
@ -92,19 +126,25 @@ impl LinksState {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_link_by_id(&self, id: usize) -> PopupResult {
|
fn open_link_by_id(&self, id: usize) -> PopupResult {
|
||||||
if let Some(link) = self.links.get(id) {
|
match self.links.get(id) {
|
||||||
// The `http://` or `https://` schema is necessary for open::that to
|
Some(Link::Url(url)) => {
|
||||||
// successfully open the link in the browser.
|
// The `http://` or `https://` schema is necessary for
|
||||||
let link = if link.starts_with("http://") || link.starts_with("https://") {
|
// open::that to successfully open the link in the browser.
|
||||||
link.clone()
|
let link = if url.starts_with("http://") || url.starts_with("https://") {
|
||||||
|
url.clone()
|
||||||
} else {
|
} else {
|
||||||
format!("https://{link}")
|
format!("https://{url}")
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(error) = open::that(&link) {
|
if let Err(error) = open::that(&link) {
|
||||||
return PopupResult::ErrorOpeningLink { link, error };
|
return PopupResult::ErrorOpeningLink { link, error };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some(Link::Room(name)) => return PopupResult::SwitchToRoom { name: name.clone() },
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
PopupResult::Handled
|
PopupResult::Handled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
use cove_config::Keys;
|
use cove_config::Keys;
|
||||||
use cove_input::InputEvent;
|
use cove_input::InputEvent;
|
||||||
use euphoxide::conn::Joined;
|
use euphoxide::conn::Joined;
|
||||||
use toss::widgets::EditorState;
|
use toss::{Style, Widget, widgets::EditorState};
|
||||||
use toss::{Style, Widget};
|
|
||||||
|
|
||||||
use crate::euph::{self, Room};
|
use crate::{
|
||||||
use crate::ui::widgets::Popup;
|
euph::{self, Room},
|
||||||
use crate::ui::{util, UiError};
|
ui::{UiError, util, widgets::Popup},
|
||||||
|
};
|
||||||
|
|
||||||
use super::popup::PopupResult;
|
use super::popup::PopupResult;
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@ pub fn new(joined: Joined) -> EditorState {
|
||||||
EditorState::with_initial_text(joined.session.name)
|
EditorState::with_initial_text(joined.session.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn widget(editor: &mut EditorState) -> impl Widget<UiError> + '_ {
|
pub fn widget(editor: &mut EditorState) -> impl Widget<UiError> {
|
||||||
let inner = editor
|
let inner = editor
|
||||||
.widget()
|
.widget()
|
||||||
.with_highlight(|s| euph::style_nick_exact(s, Style::new()));
|
.with_highlight(|s| euph::style_nick_exact(s, Style::new()));
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,31 @@
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
use crossterm::style::{Color, Stylize};
|
use crossterm::style::{Color, Stylize};
|
||||||
use euphoxide::api::{NickEvent, SessionId, SessionType, SessionView, UserId};
|
use euphoxide::{
|
||||||
use euphoxide::conn::{Joined, SessionInfo};
|
api::{NickEvent, SessionId, SessionType, SessionView, UserId},
|
||||||
use toss::widgets::{Background, Text};
|
conn::{Joined, SessionInfo},
|
||||||
use toss::{Style, Styled, Widget, WidgetExt};
|
};
|
||||||
|
use toss::{
|
||||||
|
Style, Styled, Widget, WidgetExt,
|
||||||
|
widgets::{Background, Text},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::euph;
|
use crate::{
|
||||||
use crate::ui::widgets::{ListBuilder, ListState};
|
euph,
|
||||||
use crate::ui::UiError;
|
ui::{
|
||||||
|
UiError,
|
||||||
|
widgets::{ListBuilder, ListState},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
pub fn widget<'a>(
|
pub fn widget<'a>(
|
||||||
list: &'a mut ListState<SessionId>,
|
list: &'a mut ListState<SessionId>,
|
||||||
joined: &Joined,
|
joined: &Joined,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
) -> impl Widget<UiError> + 'a {
|
nick_emoji: bool,
|
||||||
|
) -> impl Widget<UiError> + use<'a> {
|
||||||
let mut list_builder = ListBuilder::new();
|
let mut list_builder = ListBuilder::new();
|
||||||
render_rows(&mut list_builder, joined, focused);
|
render_rows(&mut list_builder, joined, focused, nick_emoji);
|
||||||
list_builder.build(list)
|
list_builder.build(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,6 +71,7 @@ fn render_rows(
|
||||||
list_builder: &mut ListBuilder<'_, SessionId, Background<Text>>,
|
list_builder: &mut ListBuilder<'_, SessionId, Background<Text>>,
|
||||||
joined: &Joined,
|
joined: &Joined,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
|
nick_emoji: bool,
|
||||||
) {
|
) {
|
||||||
let mut people = vec![];
|
let mut people = vec![];
|
||||||
let mut bots = vec![];
|
let mut bots = vec![];
|
||||||
|
|
@ -87,10 +97,38 @@ fn render_rows(
|
||||||
lurkers.sort_unstable();
|
lurkers.sort_unstable();
|
||||||
nurkers.sort_unstable();
|
nurkers.sort_unstable();
|
||||||
|
|
||||||
render_section(list_builder, "People", &people, &joined.session, focused);
|
render_section(
|
||||||
render_section(list_builder, "Bots", &bots, &joined.session, focused);
|
list_builder,
|
||||||
render_section(list_builder, "Lurkers", &lurkers, &joined.session, focused);
|
"People",
|
||||||
render_section(list_builder, "Nurkers", &nurkers, &joined.session, focused);
|
&people,
|
||||||
|
&joined.session,
|
||||||
|
focused,
|
||||||
|
nick_emoji,
|
||||||
|
);
|
||||||
|
render_section(
|
||||||
|
list_builder,
|
||||||
|
"Bots",
|
||||||
|
&bots,
|
||||||
|
&joined.session,
|
||||||
|
focused,
|
||||||
|
nick_emoji,
|
||||||
|
);
|
||||||
|
render_section(
|
||||||
|
list_builder,
|
||||||
|
"Lurkers",
|
||||||
|
&lurkers,
|
||||||
|
&joined.session,
|
||||||
|
focused,
|
||||||
|
nick_emoji,
|
||||||
|
);
|
||||||
|
render_section(
|
||||||
|
list_builder,
|
||||||
|
"Nurkers",
|
||||||
|
&nurkers,
|
||||||
|
&joined.session,
|
||||||
|
focused,
|
||||||
|
nick_emoji,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_section(
|
fn render_section(
|
||||||
|
|
@ -99,6 +137,7 @@ fn render_section(
|
||||||
sessions: &[HalfSession],
|
sessions: &[HalfSession],
|
||||||
own_session: &SessionView,
|
own_session: &SessionView,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
|
nick_emoji: bool,
|
||||||
) {
|
) {
|
||||||
if sessions.is_empty() {
|
if sessions.is_empty() {
|
||||||
return;
|
return;
|
||||||
|
|
@ -116,7 +155,7 @@ fn render_section(
|
||||||
list_builder.add_unsel(Text::new(row).background());
|
list_builder.add_unsel(Text::new(row).background());
|
||||||
|
|
||||||
for session in sessions {
|
for session in sessions {
|
||||||
render_row(list_builder, session, own_session, focused);
|
render_row(list_builder, session, own_session, focused, nick_emoji);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,6 +164,7 @@ fn render_row(
|
||||||
session: &HalfSession,
|
session: &HalfSession,
|
||||||
own_session: &SessionView,
|
own_session: &SessionView,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
|
nick_emoji: bool,
|
||||||
) {
|
) {
|
||||||
let (name, style, style_inv, perms_style_inv) = if session.name.is_empty() {
|
let (name, style, style_inv, perms_style_inv) = if session.name.is_empty() {
|
||||||
let name = "lurk".to_string();
|
let name = "lurk".to_string();
|
||||||
|
|
@ -158,16 +198,24 @@ fn render_row(
|
||||||
" "
|
" "
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let emoji = if nick_emoji {
|
||||||
|
format!(" ({})", euph::user_id_emoji(&session.id))
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
list_builder.add_sel(session.session_id.clone(), move |selected| {
|
list_builder.add_sel(session.session_id.clone(), move |selected| {
|
||||||
if focused && selected {
|
if focused && selected {
|
||||||
let text = Styled::new_plain(owner)
|
let text = Styled::new_plain(owner)
|
||||||
.then(name, style_inv)
|
.then(name, style_inv)
|
||||||
.then(perms, perms_style_inv);
|
.then(perms, perms_style_inv)
|
||||||
|
.then(emoji, perms_style_inv);
|
||||||
Text::new(text).background().with_style(style_inv)
|
Text::new(text).background().with_style(style_inv)
|
||||||
} else {
|
} else {
|
||||||
let text = Styled::new_plain(owner)
|
let text = Styled::new_plain(owner)
|
||||||
.then(&name, style)
|
.then(&name, style)
|
||||||
.then_plain(perms);
|
.then_plain(perms)
|
||||||
|
.then_plain(emoji);
|
||||||
Text::new(text).background()
|
Text::new(text).background()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,16 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use crossterm::style::Stylize;
|
use crossterm::style::Stylize;
|
||||||
use toss::widgets::Text;
|
use toss::{Style, Styled, Widget, widgets::Text};
|
||||||
use toss::{Style, Styled, Widget};
|
|
||||||
|
|
||||||
use crate::ui::widgets::Popup;
|
use crate::ui::{UiError, widgets::Popup};
|
||||||
use crate::ui::UiError;
|
|
||||||
|
|
||||||
pub enum RoomPopup {
|
pub enum RoomPopup {
|
||||||
Error { description: String, reason: String },
|
Error { description: String, reason: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RoomPopup {
|
impl RoomPopup {
|
||||||
fn server_error_widget(description: &str, reason: &str) -> impl Widget<UiError> {
|
fn server_error_widget(description: &str, reason: &str) -> impl Widget<UiError> + use<> {
|
||||||
let border_style = Style::new().red().bold();
|
let border_style = Style::new().red().bold();
|
||||||
let text = Styled::new_plain(description)
|
let text = Styled::new_plain(description)
|
||||||
.then_plain("\n\n")
|
.then_plain("\n\n")
|
||||||
|
|
@ -23,7 +21,7 @@ impl RoomPopup {
|
||||||
Popup::new(Text::new(text), ("Error", border_style)).with_border_style(border_style)
|
Popup::new(Text::new(text), ("Error", border_style)).with_border_style(border_style)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn widget(&self) -> impl Widget<UiError> {
|
pub fn widget(&self) -> impl Widget<UiError> + use<> {
|
||||||
match self {
|
match self {
|
||||||
Self::Error {
|
Self::Error {
|
||||||
description,
|
description,
|
||||||
|
|
@ -37,5 +35,6 @@ pub enum PopupResult {
|
||||||
NotHandled,
|
NotHandled,
|
||||||
Handled,
|
Handled,
|
||||||
Close,
|
Close,
|
||||||
|
SwitchToRoom { name: String },
|
||||||
ErrorOpeningLink { link: String, error: io::Error },
|
ErrorOpeningLink { link: String, error: io::Error },
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,26 +3,40 @@ use std::collections::VecDeque;
|
||||||
use cove_config::{Config, Keys};
|
use cove_config::{Config, Keys};
|
||||||
use cove_input::InputEvent;
|
use cove_input::InputEvent;
|
||||||
use crossterm::style::Stylize;
|
use crossterm::style::Stylize;
|
||||||
use euphoxide::api::{Data, Message, MessageId, PacketType, SessionId};
|
use euphoxide::{
|
||||||
use euphoxide::bot::instance::{Event, ServerConfig};
|
api::{Data, Message, MessageId, PacketType, SessionId, packet::ParsedPacket},
|
||||||
use euphoxide::conn::{self, Joined, Joining, SessionInfo};
|
bot::instance::{ConnSnapshot, Event, ServerConfig},
|
||||||
|
conn::{self, Joined, Joining, SessionInfo},
|
||||||
|
};
|
||||||
use jiff::tz::TimeZone;
|
use jiff::tz::TimeZone;
|
||||||
use tokio::sync::oneshot::error::TryRecvError;
|
use tokio::sync::{
|
||||||
use tokio::sync::{mpsc, oneshot};
|
mpsc,
|
||||||
use toss::widgets::{BoxedAsync, EditorState, Join2, Layer, Text};
|
oneshot::{self, error::TryRecvError},
|
||||||
use toss::{Style, Styled, Widget, WidgetExt};
|
};
|
||||||
|
use toss::{
|
||||||
|
Style, Styled, Widget, WidgetExt,
|
||||||
|
widgets::{BoxedAsync, EditorState, Join2, Layer, Text},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::euph;
|
use crate::{
|
||||||
use crate::macros::logging_unwrap;
|
euph::{self, SpanType},
|
||||||
use crate::ui::chat::{ChatState, Reaction};
|
macros::logging_unwrap,
|
||||||
use crate::ui::widgets::ListState;
|
ui::{
|
||||||
use crate::ui::{util, UiError, UiEvent};
|
UiError, UiEvent,
|
||||||
use crate::vault::EuphRoomVault;
|
chat::{ChatState, Reaction},
|
||||||
|
util,
|
||||||
|
widgets::ListState,
|
||||||
|
},
|
||||||
|
vault::{EuphRoomVault, RoomIdentifier},
|
||||||
|
};
|
||||||
|
|
||||||
use super::account::AccountUiState;
|
use super::{
|
||||||
use super::links::LinksState;
|
account::AccountUiState,
|
||||||
use super::popup::{PopupResult, RoomPopup};
|
auth, inspect,
|
||||||
use super::{auth, inspect, nick, nick_list};
|
links::LinksState,
|
||||||
|
nick, nick_list,
|
||||||
|
popup::{PopupResult, RoomPopup},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
enum Focus {
|
enum Focus {
|
||||||
|
|
@ -59,6 +73,8 @@ pub struct EuphRoom {
|
||||||
last_msg_sent: Option<oneshot::Receiver<MessageId>>,
|
last_msg_sent: Option<oneshot::Receiver<MessageId>>,
|
||||||
|
|
||||||
nick_list: ListState<SessionId>,
|
nick_list: ListState<SessionId>,
|
||||||
|
|
||||||
|
mentioned: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EuphRoom {
|
impl EuphRoom {
|
||||||
|
|
@ -82,6 +98,7 @@ impl EuphRoom {
|
||||||
chat: ChatState::new(vault, tz),
|
chat: ChatState::new(vault, tz),
|
||||||
last_msg_sent: None,
|
last_msg_sent: None,
|
||||||
nick_list: ListState::new(),
|
nick_list: ListState::new(),
|
||||||
|
mentioned: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,7 +121,7 @@ impl EuphRoom {
|
||||||
.server_config
|
.server_config
|
||||||
.clone()
|
.clone()
|
||||||
.room(self.vault().room().name.clone())
|
.room(self.vault().room().name.clone())
|
||||||
.name(format!("{room:?}-{}", next_instance_id))
|
.name(format!("{room:?}-{next_instance_id}"))
|
||||||
.human(true)
|
.human(true)
|
||||||
.username(self.room_config.username.clone())
|
.username(self.room_config.username.clone())
|
||||||
.force_username(self.room_config.force_username)
|
.force_username(self.room_config.force_username)
|
||||||
|
|
@ -150,6 +167,12 @@ impl EuphRoom {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn retrieve_mentioned(&mut self) -> bool {
|
||||||
|
let mentioned = self.mentioned;
|
||||||
|
self.mentioned = false;
|
||||||
|
mentioned
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn unseen_msgs_count(&self) -> usize {
|
pub async fn unseen_msgs_count(&self) -> usize {
|
||||||
logging_unwrap!(self.vault().unseen_msgs_count().await)
|
logging_unwrap!(self.vault().unseen_msgs_count().await)
|
||||||
}
|
}
|
||||||
|
|
@ -268,7 +291,12 @@ impl EuphRoom {
|
||||||
joined: &Joined,
|
joined: &Joined,
|
||||||
focus: Focus,
|
focus: Focus,
|
||||||
) -> BoxedAsync<'a, UiError> {
|
) -> BoxedAsync<'a, UiError> {
|
||||||
let nick_list_widget = nick_list::widget(nick_list, joined, focus == Focus::NickList)
|
let nick_list_widget = nick_list::widget(
|
||||||
|
nick_list,
|
||||||
|
joined,
|
||||||
|
focus == Focus::NickList,
|
||||||
|
chat.nick_emoji(),
|
||||||
|
)
|
||||||
.padding()
|
.padding()
|
||||||
.with_right(1)
|
.with_right(1)
|
||||||
.border()
|
.border()
|
||||||
|
|
@ -287,7 +315,7 @@ impl EuphRoom {
|
||||||
.boxed_async()
|
.boxed_async()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn status_widget(&self, state: Option<&euph::State>) -> impl Widget<UiError> {
|
async fn status_widget(&self, state: Option<&euph::State>) -> impl Widget<UiError> + use<> {
|
||||||
let room_style = Style::new().bold().blue();
|
let room_style = Style::new().bold().blue();
|
||||||
let mut info = Styled::new(format!("{} ", self.domain()), Style::new().grey())
|
let mut info = Styled::new(format!("{} ", self.domain()), Style::new().grey())
|
||||||
.then(format!("&{}", self.name()), room_style);
|
.then(format!("&{}", self.name()), room_style);
|
||||||
|
|
@ -486,18 +514,22 @@ impl EuphRoom {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_input_event(&mut self, event: &mut InputEvent<'_>, keys: &Keys) -> bool {
|
pub async fn handle_input_event(
|
||||||
|
&mut self,
|
||||||
|
event: &mut InputEvent<'_>,
|
||||||
|
keys: &Keys,
|
||||||
|
) -> RoomResult {
|
||||||
if !self.popups.is_empty() {
|
if !self.popups.is_empty() {
|
||||||
if event.matches(&keys.general.abort) {
|
if event.matches(&keys.general.abort) {
|
||||||
self.popups.pop_back();
|
self.popups.pop_back();
|
||||||
return true;
|
return RoomResult::Handled;
|
||||||
}
|
}
|
||||||
// Prevent event from reaching anything below the popup
|
// Prevent event from reaching anything below the popup
|
||||||
return false;
|
return RoomResult::NotHandled;
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = match &mut self.state {
|
let result = match &mut self.state {
|
||||||
State::Normal => return self.handle_normal_input_event(event, keys).await,
|
State::Normal => return self.handle_normal_input_event(event, keys).await.into(),
|
||||||
State::Auth(editor) => auth::handle_input_event(event, keys, &self.room, editor),
|
State::Auth(editor) => auth::handle_input_event(event, keys, &self.room, editor),
|
||||||
State::Nick(editor) => nick::handle_input_event(event, keys, &self.room, editor),
|
State::Nick(editor) => nick::handle_input_event(event, keys, &self.room, editor),
|
||||||
State::Account(account) => account.handle_input_event(event, keys, &self.room),
|
State::Account(account) => account.handle_input_event(event, keys, &self.room),
|
||||||
|
|
@ -508,18 +540,24 @@ impl EuphRoom {
|
||||||
};
|
};
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
PopupResult::NotHandled => false,
|
PopupResult::NotHandled => RoomResult::NotHandled,
|
||||||
PopupResult::Handled => true,
|
PopupResult::Handled => RoomResult::Handled,
|
||||||
PopupResult::Close => {
|
PopupResult::Close => {
|
||||||
self.state = State::Normal;
|
self.state = State::Normal;
|
||||||
true
|
RoomResult::Handled
|
||||||
}
|
}
|
||||||
|
PopupResult::SwitchToRoom { name } => RoomResult::SwitchToRoom {
|
||||||
|
room: RoomIdentifier {
|
||||||
|
domain: self.vault().room().domain.clone(),
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
},
|
||||||
PopupResult::ErrorOpeningLink { link, error } => {
|
PopupResult::ErrorOpeningLink { link, error } => {
|
||||||
self.popups.push_front(RoomPopup::Error {
|
self.popups.push_front(RoomPopup::Error {
|
||||||
description: format!("Failed to open link: {link}"),
|
description: format!("Failed to open link: {link}"),
|
||||||
reason: format!("{error}"),
|
reason: format!("{error}"),
|
||||||
});
|
});
|
||||||
true
|
RoomResult::Handled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -533,6 +571,35 @@ impl EuphRoom {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Event::Packet(
|
||||||
|
_,
|
||||||
|
ParsedPacket {
|
||||||
|
content: Ok(Data::SendEvent(send)),
|
||||||
|
..
|
||||||
|
},
|
||||||
|
ConnSnapshot {
|
||||||
|
state: conn::State::Joined(joined),
|
||||||
|
..
|
||||||
|
},
|
||||||
|
) = &event
|
||||||
|
{
|
||||||
|
let normalized_name = euphoxide::nick::normalize(&joined.session.name);
|
||||||
|
let content = &*send.0.content;
|
||||||
|
for (rtype, rspan) in euph::find_spans(content) {
|
||||||
|
if rtype != SpanType::Mention {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let Some(mention) = content[rspan].strip_prefix('@') else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let normalized_mention = euphoxide::nick::normalize(mention);
|
||||||
|
if normalized_name == normalized_mention {
|
||||||
|
self.mentioned = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We handle the packet internally first because the room event handling
|
// We handle the packet internally first because the room event handling
|
||||||
// will consume it while we only need a reference.
|
// will consume it while we only need a reference.
|
||||||
let handled = if let Event::Packet(_, packet, _) = &event {
|
let handled = if let Event::Packet(_, packet, _) = &event {
|
||||||
|
|
@ -624,3 +691,18 @@ impl EuphRoom {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum RoomResult {
|
||||||
|
NotHandled,
|
||||||
|
Handled,
|
||||||
|
SwitchToRoom { room: RoomIdentifier },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for RoomResult {
|
||||||
|
fn from(value: bool) -> Self {
|
||||||
|
match value {
|
||||||
|
true => Self::Handled,
|
||||||
|
false => Self::NotHandled,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,15 @@ use std::convert::Infallible;
|
||||||
use cove_config::{Config, Keys};
|
use cove_config::{Config, Keys};
|
||||||
use cove_input::{InputEvent, KeyBinding, KeyBindingInfo, KeyGroupInfo};
|
use cove_input::{InputEvent, KeyBinding, KeyBindingInfo, KeyGroupInfo};
|
||||||
use crossterm::style::Stylize;
|
use crossterm::style::Stylize;
|
||||||
use toss::widgets::{Either2, Join2, Padding, Text};
|
use toss::{
|
||||||
use toss::{Style, Styled, Widget, WidgetExt};
|
Style, Styled, Widget, WidgetExt,
|
||||||
|
widgets::{Either2, Join2, Padding, Text},
|
||||||
|
};
|
||||||
|
|
||||||
use super::widgets::{ListBuilder, ListState, Popup};
|
use super::{
|
||||||
use super::{util, UiError};
|
UiError, util,
|
||||||
|
widgets::{ListBuilder, ListState, Popup},
|
||||||
|
};
|
||||||
|
|
||||||
type Line = Either2<Text, Join2<Padding<Text>, Text>>;
|
type Line = Either2<Text, Join2<Padding<Text>, Text>>;
|
||||||
type Builder = ListBuilder<'static, Infallible, Line>;
|
type Builder = ListBuilder<'static, Infallible, Line>;
|
||||||
|
|
@ -69,7 +73,7 @@ fn render_group_info(builder: &mut Builder, group_info: KeyGroupInfo<'_>) {
|
||||||
pub fn widget<'a>(
|
pub fn widget<'a>(
|
||||||
list: &'a mut ListState<Infallible>,
|
list: &'a mut ListState<Infallible>,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> impl Widget<UiError> + 'a {
|
) -> impl Widget<UiError> + use<'a> {
|
||||||
let mut list_builder = ListBuilder::new();
|
let mut list_builder = ListBuilder::new();
|
||||||
|
|
||||||
for group_info in config.keys.groups() {
|
for group_info in config.keys.groups() {
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,46 @@
|
||||||
mod connect;
|
use std::{
|
||||||
mod delete;
|
collections::{HashMap, HashSet, hash_map::Entry},
|
||||||
|
iter,
|
||||||
use std::collections::hash_map::Entry;
|
sync::{Arc, Mutex},
|
||||||
use std::collections::{HashMap, HashSet};
|
time::Duration,
|
||||||
use std::iter;
|
};
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use cove_config::{Config, Keys, RoomsSortOrder};
|
use cove_config::{Config, Keys, RoomsSortOrder};
|
||||||
use cove_input::InputEvent;
|
use cove_input::InputEvent;
|
||||||
use crossterm::style::Stylize;
|
use crossterm::style::Stylize;
|
||||||
use euphoxide::api::SessionType;
|
use euphoxide::{
|
||||||
use euphoxide::bot::instance::{Event, ServerConfig};
|
api::SessionType,
|
||||||
use euphoxide::conn::{self, Joined};
|
bot::instance::{Event, ServerConfig},
|
||||||
|
conn::{self, Joined},
|
||||||
|
};
|
||||||
use jiff::tz::TimeZone;
|
use jiff::tz::TimeZone;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use toss::widgets::{BoxedAsync, Empty, Join2, Text};
|
use toss::{
|
||||||
use toss::{Style, Styled, Widget, WidgetExt};
|
Style, Styled, Widget, WidgetExt,
|
||||||
|
widgets::{BellState, BoxedAsync, Empty, Join2, Text},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::euph;
|
use crate::{
|
||||||
use crate::macros::logging_unwrap;
|
euph,
|
||||||
use crate::vault::{EuphVault, RoomIdentifier, Vault};
|
macros::logging_unwrap,
|
||||||
use crate::version::{NAME, VERSION};
|
vault::{EuphVault, RoomIdentifier, Vault},
|
||||||
|
version::{NAME, VERSION},
|
||||||
|
};
|
||||||
|
|
||||||
use self::connect::{ConnectResult, ConnectState};
|
use super::{
|
||||||
use self::delete::{DeleteResult, DeleteState};
|
UiError, UiEvent,
|
||||||
|
euph::room::{EuphRoom, RoomResult},
|
||||||
|
key_bindings, util,
|
||||||
|
widgets::{ListBuilder, ListState},
|
||||||
|
};
|
||||||
|
|
||||||
use super::euph::room::EuphRoom;
|
use self::{
|
||||||
use super::widgets::{ListBuilder, ListState};
|
connect::{ConnectResult, ConnectState},
|
||||||
use super::{key_bindings, util, UiError, UiEvent};
|
delete::{DeleteResult, DeleteState},
|
||||||
|
};
|
||||||
|
|
||||||
|
mod connect;
|
||||||
|
mod delete;
|
||||||
|
|
||||||
enum State {
|
enum State {
|
||||||
ShowList,
|
ShowList,
|
||||||
|
|
@ -83,6 +95,7 @@ pub struct Rooms {
|
||||||
|
|
||||||
list: ListState<RoomIdentifier>,
|
list: ListState<RoomIdentifier>,
|
||||||
order: Order,
|
order: Order,
|
||||||
|
bell: BellState,
|
||||||
|
|
||||||
euph_servers: HashMap<String, EuphServer>,
|
euph_servers: HashMap<String, EuphServer>,
|
||||||
euph_rooms: HashMap<RoomIdentifier, EuphRoom>,
|
euph_rooms: HashMap<RoomIdentifier, EuphRoom>,
|
||||||
|
|
@ -103,6 +116,7 @@ impl Rooms {
|
||||||
state: State::ShowList,
|
state: State::ShowList,
|
||||||
list: ListState::new(),
|
list: ListState::new(),
|
||||||
order: Order::from_rooms_sort_order(config.rooms_sort_order),
|
order: Order::from_rooms_sort_order(config.rooms_sort_order),
|
||||||
|
bell: BellState::new(),
|
||||||
euph_servers: HashMap::new(),
|
euph_servers: HashMap::new(),
|
||||||
euph_rooms: HashMap::new(),
|
euph_rooms: HashMap::new(),
|
||||||
};
|
};
|
||||||
|
|
@ -232,7 +246,9 @@ impl Rooms {
|
||||||
.retain(|n, r| !r.stopped() || rooms_set.contains(n));
|
.retain(|n, r| !r.stopped() || rooms_set.contains(n));
|
||||||
|
|
||||||
for room in rooms_set {
|
for room in rooms_set {
|
||||||
self.get_or_insert_room(room).await.retain();
|
let room = self.get_or_insert_room(room).await;
|
||||||
|
room.retain();
|
||||||
|
self.bell.ring |= room.retrieve_mentioned();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -242,7 +258,7 @@ impl Rooms {
|
||||||
_ => self.stabilize_rooms().await,
|
_ => self.stabilize_rooms().await,
|
||||||
}
|
}
|
||||||
|
|
||||||
match &mut self.state {
|
let widget = match &mut self.state {
|
||||||
State::ShowList => Self::rooms_widget(
|
State::ShowList => Self::rooms_widget(
|
||||||
&self.vault,
|
&self.vault,
|
||||||
self.config,
|
self.config,
|
||||||
|
|
@ -285,6 +301,12 @@ impl Rooms {
|
||||||
.below(delete.widget())
|
.below(delete.widget())
|
||||||
.desync()
|
.desync()
|
||||||
.boxed_async(),
|
.boxed_async(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.config.bell_on_mention {
|
||||||
|
widget.above(self.bell.widget().desync()).boxed_async()
|
||||||
|
} else {
|
||||||
|
widget
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -423,7 +445,7 @@ impl Rooms {
|
||||||
list: &'a mut ListState<RoomIdentifier>,
|
list: &'a mut ListState<RoomIdentifier>,
|
||||||
order: Order,
|
order: Order,
|
||||||
euph_rooms: &HashMap<RoomIdentifier, EuphRoom>,
|
euph_rooms: &HashMap<RoomIdentifier, EuphRoom>,
|
||||||
) -> impl Widget<UiError> + 'a {
|
) -> impl Widget<UiError> + use<'a> {
|
||||||
let version_info = Styled::new_plain("Welcome to ")
|
let version_info = Styled::new_plain("Welcome to ")
|
||||||
.then(format!("{NAME} {VERSION}"), Style::new().yellow().bold())
|
.then(format!("{NAME} {VERSION}"), Style::new().yellow().bold())
|
||||||
.then_plain("!");
|
.then_plain("!");
|
||||||
|
|
@ -514,7 +536,10 @@ impl Rooms {
|
||||||
}
|
}
|
||||||
if event.matches(&keys.rooms.action.connect_autojoin) {
|
if event.matches(&keys.rooms.action.connect_autojoin) {
|
||||||
for (domain, server) in &self.config.euph.servers {
|
for (domain, server) in &self.config.euph.servers {
|
||||||
for name in server.rooms.keys() {
|
for (name, room) in &server.rooms {
|
||||||
|
if !room.autojoin {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let id = RoomIdentifier::new(domain.clone(), name.clone());
|
let id = RoomIdentifier::new(domain.clone(), name.clone());
|
||||||
self.connect_to_room(id).await;
|
self.connect_to_room(id).await;
|
||||||
}
|
}
|
||||||
|
|
@ -562,9 +587,16 @@ impl Rooms {
|
||||||
}
|
}
|
||||||
State::ShowRoom(name) => {
|
State::ShowRoom(name) => {
|
||||||
if let Some(room) = self.euph_rooms.get_mut(name) {
|
if let Some(room) = self.euph_rooms.get_mut(name) {
|
||||||
if room.handle_input_event(event, keys).await {
|
match room.handle_input_event(event, keys).await {
|
||||||
|
RoomResult::NotHandled => {}
|
||||||
|
RoomResult::Handled => return true,
|
||||||
|
RoomResult::SwitchToRoom { room } => {
|
||||||
|
self.list.move_cursor_to_id(&room);
|
||||||
|
self.connect_to_room(room.clone()).await;
|
||||||
|
self.state = State::ShowRoom(room);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if event.matches(&keys.general.abort) {
|
if event.matches(&keys.general.abort) {
|
||||||
self.state = State::ShowList;
|
self.state = State::ShowList;
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -577,6 +609,7 @@ impl Rooms {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
ConnectResult::Connect(room) => {
|
ConnectResult::Connect(room) => {
|
||||||
|
self.list.move_cursor_to_id(&room);
|
||||||
self.connect_to_room(room.clone()).await;
|
self.connect_to_room(room.clone()).await;
|
||||||
self.state = State::ShowRoom(room);
|
self.state = State::ShowRoom(room);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
use cove_config::Keys;
|
use cove_config::Keys;
|
||||||
use cove_input::InputEvent;
|
use cove_input::InputEvent;
|
||||||
use crossterm::style::Stylize;
|
use crossterm::style::Stylize;
|
||||||
use toss::widgets::{EditorState, Empty, Join2, Join3, Text};
|
use toss::{
|
||||||
use toss::{Style, Styled, Widget, WidgetExt};
|
Style, Styled, Widget, WidgetExt,
|
||||||
|
widgets::{EditorState, Empty, Join2, Join3, Text},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::ui::widgets::Popup;
|
use crate::{
|
||||||
use crate::ui::{util, UiError};
|
ui::{UiError, util, widgets::Popup},
|
||||||
use crate::vault::RoomIdentifier;
|
vault::RoomIdentifier,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
enum Focus {
|
enum Focus {
|
||||||
|
|
@ -81,7 +84,7 @@ impl ConnectState {
|
||||||
ConnectResult::Unhandled
|
ConnectResult::Unhandled
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn widget(&mut self) -> impl Widget<UiError> + '_ {
|
pub fn widget(&mut self) -> impl Widget<UiError> {
|
||||||
let room_style = Style::new().bold().blue();
|
let room_style = Style::new().bold().blue();
|
||||||
let domain_style = Style::new().grey();
|
let domain_style = Style::new().grey();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
use cove_config::Keys;
|
use cove_config::Keys;
|
||||||
use cove_input::InputEvent;
|
use cove_input::InputEvent;
|
||||||
use crossterm::style::Stylize;
|
use crossterm::style::Stylize;
|
||||||
use toss::widgets::{EditorState, Empty, Join2, Text};
|
use toss::{
|
||||||
use toss::{Style, Styled, Widget, WidgetExt};
|
Style, Styled, Widget, WidgetExt,
|
||||||
|
widgets::{EditorState, Empty, Join2, Text},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::ui::widgets::Popup;
|
use crate::{
|
||||||
use crate::ui::{util, UiError};
|
ui::{UiError, util, widgets::Popup},
|
||||||
use crate::vault::RoomIdentifier;
|
vault::RoomIdentifier,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct DeleteState {
|
pub struct DeleteState {
|
||||||
id: RoomIdentifier,
|
id: RoomIdentifier,
|
||||||
|
|
@ -44,7 +47,7 @@ impl DeleteState {
|
||||||
DeleteResult::Unhandled
|
DeleteResult::Unhandled
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn widget(&mut self) -> impl Widget<UiError> + '_ {
|
pub fn widget(&mut self) -> impl Widget<UiError> {
|
||||||
let warn_style = Style::new().bold().red();
|
let warn_style = Style::new().bold().red();
|
||||||
let room_style = Style::new().bold().blue();
|
let room_style = Style::new().bold().blue();
|
||||||
let text = Styled::new_plain("Are you sure you want to delete ")
|
let text = Styled::new_plain("Are you sure you want to delete ")
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
mod list;
|
|
||||||
mod popup;
|
|
||||||
|
|
||||||
pub use self::list::*;
|
pub use self::list::*;
|
||||||
pub use self::popup::*;
|
pub use self::popup::*;
|
||||||
|
|
||||||
|
mod list;
|
||||||
|
mod popup;
|
||||||
|
|
|
||||||
|
|
@ -239,6 +239,12 @@ impl<Id: Clone + Eq> ListState<Id> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn move_cursor_to_id(&mut self, id: &Id) {
|
||||||
|
if let Some(new_cursor) = self.selectable_of_id(id) {
|
||||||
|
self.move_cursor_to(new_cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn fix_cursor(&mut self) {
|
fn fix_cursor(&mut self) {
|
||||||
let new_cursor = if let Some(cursor) = &self.cursor {
|
let new_cursor = if let Some(cursor) = &self.cursor {
|
||||||
self.selectable_of_id(&cursor.id)
|
self.selectable_of_id(&cursor.id)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use toss::widgets::{Background, Border, Desync, Float, Layer2, Padding, Text};
|
use toss::{
|
||||||
use toss::{Frame, Size, Style, Styled, Widget, WidgetExt, WidthDb};
|
Frame, Size, Style, Styled, Widget, WidgetExt, WidthDb,
|
||||||
|
widgets::{Background, Border, Desync, Float, Layer2, Padding, Text},
|
||||||
|
};
|
||||||
|
|
||||||
type Body<I> = Background<Border<Padding<I>>>;
|
type Body<I> = Background<Border<Padding<I>>>;
|
||||||
type Title = Float<Padding<Background<Padding<Text>>>>;
|
type Title = Float<Padding<Background<Padding<Text>>>>;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use std::convert::Infallible;
|
use std::{convert::Infallible, env};
|
||||||
use std::env;
|
|
||||||
|
|
||||||
use jiff::tz::TimeZone;
|
use jiff::tz::TimeZone;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
|
use rusqlite::Connection;
|
||||||
|
use vault::{Action, tokio::TokioVault};
|
||||||
|
|
||||||
|
pub use self::euph::{EuphRoomVault, EuphVault, RoomIdentifier};
|
||||||
|
|
||||||
mod euph;
|
mod euph;
|
||||||
mod migrate;
|
mod migrate;
|
||||||
mod prepare;
|
mod prepare;
|
||||||
|
|
||||||
use std::fs;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use rusqlite::Connection;
|
|
||||||
use vault::tokio::TokioVault;
|
|
||||||
use vault::Action;
|
|
||||||
|
|
||||||
pub use self::euph::{EuphRoomVault, EuphVault, RoomIdentifier};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Vault {
|
pub struct Vault {
|
||||||
tokio_vault: TokioVault,
|
tokio_vault: TokioVault,
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,25 @@
|
||||||
use std::str::FromStr;
|
use std::{fmt, mem, str::FromStr};
|
||||||
use std::time::Instant;
|
|
||||||
use std::{fmt, mem};
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use cookie::{Cookie, CookieJar};
|
use cookie::{Cookie, CookieJar};
|
||||||
use euphoxide::api::{Message, MessageId, SessionId, SessionView, Snowflake, Time, UserId};
|
use euphoxide::api::{Message, MessageId, SessionId, SessionView, Snowflake, Time, UserId};
|
||||||
use rusqlite::types::{FromSql, FromSqlError, ToSqlOutput, Value, ValueRef};
|
use rusqlite::{
|
||||||
use rusqlite::{named_params, params, Connection, OptionalExtension, Row, ToSql, Transaction};
|
Connection, OptionalExtension, Row, ToSql, Transaction, named_params, params,
|
||||||
|
types::{FromSql, FromSqlError, ToSqlOutput, Value, ValueRef},
|
||||||
|
};
|
||||||
use vault::Action;
|
use vault::Action;
|
||||||
|
|
||||||
use crate::euph::SmallMessage;
|
use crate::{
|
||||||
use crate::store::{MsgStore, Path, Tree};
|
euph::SmallMessage,
|
||||||
|
store::{MsgStore, Path, Tree},
|
||||||
|
};
|
||||||
|
|
||||||
/// Wrapper for [`Snowflake`] that implements useful rusqlite traits.
|
/// Wrapper for [`Snowflake`] that implements useful rusqlite traits.
|
||||||
struct WSnowflake(Snowflake);
|
struct WSnowflake(Snowflake);
|
||||||
|
|
||||||
impl ToSql for WSnowflake {
|
impl ToSql for WSnowflake {
|
||||||
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
||||||
self.0 .0.to_sql()
|
self.0.0.to_sql()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,7 +34,7 @@ struct WTime(Time);
|
||||||
|
|
||||||
impl ToSql for WTime {
|
impl ToSql for WTime {
|
||||||
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
||||||
let timestamp = self.0 .0;
|
let timestamp = self.0.0;
|
||||||
Ok(ToSqlOutput::Owned(Value::Integer(timestamp)))
|
Ok(ToSqlOutput::Owned(Value::Integer(timestamp)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -609,7 +611,7 @@ impl Action for GetMsg {
|
||||||
let msg = conn
|
let msg = conn
|
||||||
.query_row(
|
.query_row(
|
||||||
"
|
"
|
||||||
SELECT id, parent, time, name, content, seen
|
SELECT id, parent, time, user_id, name, content, seen
|
||||||
FROM euph_msgs
|
FROM euph_msgs
|
||||||
WHERE domain = ?
|
WHERE domain = ?
|
||||||
AND room = ?
|
AND room = ?
|
||||||
|
|
@ -621,9 +623,10 @@ impl Action for GetMsg {
|
||||||
id: MessageId(row.get::<_, WSnowflake>(0)?.0),
|
id: MessageId(row.get::<_, WSnowflake>(0)?.0),
|
||||||
parent: row.get::<_, Option<WSnowflake>>(1)?.map(|s| MessageId(s.0)),
|
parent: row.get::<_, Option<WSnowflake>>(1)?.map(|s| MessageId(s.0)),
|
||||||
time: row.get::<_, WTime>(2)?.0,
|
time: row.get::<_, WTime>(2)?.0,
|
||||||
nick: row.get(3)?,
|
user_id: UserId(row.get(3)?),
|
||||||
content: row.get(4)?,
|
nick: row.get(4)?,
|
||||||
seen: row.get(5)?,
|
content: row.get(5)?,
|
||||||
|
seen: row.get(6)?,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -687,12 +690,12 @@ impl Action for GetTree {
|
||||||
type Error = rusqlite::Error;
|
type Error = rusqlite::Error;
|
||||||
|
|
||||||
fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> {
|
fn run(self, conn: &mut Connection) -> Result<Self::Output, Self::Error> {
|
||||||
let start = Instant::now();
|
let msgs = conn
|
||||||
|
.prepare(
|
||||||
let query = "
|
"
|
||||||
WITH RECURSIVE
|
WITH RECURSIVE
|
||||||
tree (domain, room, id) AS (
|
tree (domain, room, id) AS (
|
||||||
VALUES (:domain, :room, :id)
|
VALUES (?, ?, ?)
|
||||||
UNION
|
UNION
|
||||||
SELECT euph_msgs.domain, euph_msgs.room, euph_msgs.id
|
SELECT euph_msgs.domain, euph_msgs.room, euph_msgs.id
|
||||||
FROM euph_msgs
|
FROM euph_msgs
|
||||||
|
|
@ -701,49 +704,27 @@ impl Action for GetTree {
|
||||||
AND tree.room = euph_msgs.room
|
AND tree.room = euph_msgs.room
|
||||||
AND tree.id = euph_msgs.parent
|
AND tree.id = euph_msgs.parent
|
||||||
)
|
)
|
||||||
SELECT id, parent, time, 'name', 'content', 1
|
SELECT id, parent, time, user_id, name, content, seen
|
||||||
FROM euph_msgs
|
FROM euph_msgs
|
||||||
JOIN tree USING (domain, room, id)
|
JOIN tree USING (domain, room, id)
|
||||||
ORDER BY id ASC
|
ORDER BY id ASC
|
||||||
";
|
",
|
||||||
|
)?
|
||||||
let mut statement = conn.prepare(&format!("EXPLAIN QUERY PLAN {query}"))?;
|
|
||||||
let mut rows = statement.query(named_params! {
|
|
||||||
":domain": self.room.domain,
|
|
||||||
":room": self.room.name,
|
|
||||||
":id": WSnowflake(self.root_id.0),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
while let Some(row) = rows.next()? {
|
|
||||||
let id = row.get::<_, i64>("id")?;
|
|
||||||
let parent = row.get::<_, i64>("parent")?;
|
|
||||||
let notused = row.get::<_, i64>("notused")?;
|
|
||||||
let detail = row.get::<_, String>("detail")?;
|
|
||||||
eprintln!("{parent:3} -> {id:3} (notused {notused:3}): {detail}");
|
|
||||||
}
|
|
||||||
|
|
||||||
let msgs = conn
|
|
||||||
.prepare(query)?
|
|
||||||
.query_map(
|
.query_map(
|
||||||
named_params! {
|
params![self.room.domain, self.room.name, WSnowflake(self.root_id.0)],
|
||||||
":domain": self.room.domain,
|
|
||||||
":room": self.room.name,
|
|
||||||
":id": WSnowflake(self.root_id.0),
|
|
||||||
},
|
|
||||||
|row| {
|
|row| {
|
||||||
Ok(SmallMessage {
|
Ok(SmallMessage {
|
||||||
id: MessageId(row.get::<_, WSnowflake>(0)?.0),
|
id: MessageId(row.get::<_, WSnowflake>(0)?.0),
|
||||||
parent: row.get::<_, Option<WSnowflake>>(1)?.map(|s| MessageId(s.0)),
|
parent: row.get::<_, Option<WSnowflake>>(1)?.map(|s| MessageId(s.0)),
|
||||||
time: row.get::<_, WTime>(2)?.0,
|
time: row.get::<_, WTime>(2)?.0,
|
||||||
nick: row.get(3)?,
|
user_id: UserId(row.get(3)?),
|
||||||
content: row.get(4)?,
|
nick: row.get(4)?,
|
||||||
seen: row.get(5)?,
|
content: row.get(5)?,
|
||||||
|
seen: row.get(6)?,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
)?
|
)?
|
||||||
.collect::<rusqlite::Result<_>>()?;
|
.collect::<rusqlite::Result<_>>()?;
|
||||||
let end = Instant::now();
|
|
||||||
eprintln!("{:10}", end.duration_since(start).as_micros());
|
|
||||||
Ok(Tree::new(self.root_id, msgs))
|
Ok(Tree::new(self.root_id, msgs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
47
flake.lock
generated
47
flake.lock
generated
|
|
@ -1,47 +0,0 @@
|
||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"naersk": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1713520724,
|
|
||||||
"narHash": "sha256-CO8MmVDmqZX2FovL75pu5BvwhW+Vugc7Q6ze7Hj8heI=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "naersk",
|
|
||||||
"rev": "c5037590290c6c7dae2e42e7da1e247e54ed2d49",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "naersk",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1714068967,
|
|
||||||
"narHash": "sha256-jfQUewdwBVs0HHLH10qxyn0+J53e1aQoPSkuBnYf15s=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "10b682b6e5ed139ee2bef863ada3043f2d79c1cc",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"naersk": "naersk",
|
|
||||||
"nixpkgs": "nixpkgs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
||||||
29
flake.nix
29
flake.nix
|
|
@ -1,29 +0,0 @@
|
||||||
{
|
|
||||||
description = "TUI client for euphoria.leet.nu, a threaded real-time chat platform";
|
|
||||||
|
|
||||||
inputs = {
|
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs";
|
|
||||||
|
|
||||||
naersk.url = "github:nix-community/naersk";
|
|
||||||
naersk.inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
|
|
||||||
outputs = { self, nixpkgs, naersk }:
|
|
||||||
let forAllSystems = nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed;
|
|
||||||
in {
|
|
||||||
packages = forAllSystems (system:
|
|
||||||
let
|
|
||||||
pkgs = import nixpkgs { inherit system; };
|
|
||||||
naersk' = pkgs.callPackage naersk { };
|
|
||||||
cargoToml = pkgs.lib.importTOML ./Cargo.toml;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
default = naersk'.buildPackage {
|
|
||||||
name = "cove";
|
|
||||||
version = cargoToml.workspace.package.version;
|
|
||||||
root = ./.;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue