-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 5149031
Showing
15 changed files
with
1,707 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
name: Rust ✅ | ||
|
||
on: | ||
workflow_dispatch: | ||
push: | ||
paths: | ||
- "*" | ||
pull_request: | ||
paths: | ||
- "*" | ||
|
||
env: | ||
CARGO_TERM_COLOR: always | ||
|
||
jobs: | ||
check: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout 🛒 | ||
uses: actions/checkout@v3 | ||
|
||
- name: Toolchain 🧰 | ||
uses: actions-rs/toolchain@v1 | ||
with: | ||
profile: minimal | ||
toolchain: stable | ||
components: rustfmt, clippy | ||
override: true | ||
|
||
- name: Check ✅ | ||
uses: actions-rs/cargo@v1 | ||
continue-on-error: false | ||
with: | ||
command: check | ||
args: --verbose |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# Rust | ||
target | ||
Cargo.lock | ||
|
||
# CLion | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// This file is part of Germ <https://github.com/gemrest/sydney>. | ||
// | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, version 3. | ||
// | ||
// This program is distributed in the hope that it will be useful, but | ||
// WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
// General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
// | ||
// Copyright (C) {20\d{2}(-20\d{2})?} Fuwn <[email protected]> | ||
// SPDX-License-Identifier: GPL-3.0-only |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[package] | ||
name = "syndey" | ||
version = "0.1.0" | ||
authors = ["Fuwn <[email protected]>"] | ||
edition = "2021" | ||
description = "Vim-like, Command-line Gemini Client" | ||
readme = "README.md" | ||
homepage = "https://github.com/gemrest/sydney" | ||
repository = "https://github.com/gemrest/sydney" | ||
license = "GPL-3.0-only" | ||
keywords = ["gemini", "client"] | ||
|
||
[dependencies] | ||
germ = { version = "0.3.3", default-features = false, features = ["request", "ast"] } # Gemini | ||
url = "2.2.2" # URL | ||
tui = "0.18.0" # Terminal User Interface | ||
crossterm = "0.24.0" # Cross-platform Terminal |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
[tasks.fmt] | ||
args = ["fmt"] | ||
command = "cargo" | ||
toolchain = "nightly" | ||
workspace = false | ||
|
||
[tasks.check] | ||
args = ["check", "--all-features"] | ||
command = "cargo" | ||
workspace = false | ||
|
||
[tasks.clippy] | ||
args = ["clippy", "--all-features"] | ||
command = "cargo" | ||
workspace = false | ||
|
||
[tasks.test] | ||
args = ["test", "--all-features"] | ||
command = "cargo" | ||
workspace = false | ||
|
||
[tasks.checkf] | ||
dependencies = ["fmt", "check"] | ||
workspace = false | ||
|
||
[tasks.checkfc] | ||
dependencies = ["fmt", "check", "clippy"] | ||
workspace = false | ||
|
||
[tasks.run] | ||
args = ["run", "--", "${@}"] | ||
command = "cargo" | ||
workspace = false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# Sydney | ||
|
||
[](https://crates.io/crates/sydney) | ||
[](https://github.com/gemrest/sydney/actions/workflows/rust.yaml) | ||
|
||
Sydney is a Vim-like, command-line Gemini client. | ||
|
||
Sydney has a beautiful, intuitive, and powerful command-line interface; | ||
including: | ||
|
||
- Vim-like keybindings | ||
- Vim-like commands | ||
- Intuitive link handling | ||
- Understandable errors | ||
- Customizable UI | ||
|
||
## Usage | ||
|
||
### Installation | ||
|
||
```shell | ||
cargo install sydney --force | ||
``` | ||
|
||
### Help | ||
|
||
```shell | ||
usage: syndey [option, capsule_uri] | ||
Options: | ||
--version, -v show version text | ||
--help, -h show help text | ||
|
||
Sample invocations: | ||
syndey gemini://gem.rest/ | ||
syndey --help | ||
|
||
Report bugs to https://github.com/gemrest/sydney/issues | ||
|
||
``` | ||
|
||
## License | ||
|
||
This project is licensed with the [GNU General Public License v3.0](https://github.com/gemrest/sydney/blob/main/LICENSE). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[toolchain] | ||
channel = "1.59.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
condense_wildcard_suffixes = true | ||
edition = "2021" | ||
enum_discrim_align_threshold = 20 | ||
# error_on_line_overflow = true | ||
# error_on_unformatted = true | ||
fn_single_line = true | ||
force_multiline_blocks = true | ||
format_code_in_doc_comments = true | ||
format_macro_matchers = true | ||
format_strings = true | ||
imports_layout = "HorizontalVertical" | ||
license_template_path = ".license_template" | ||
max_width = 80 | ||
match_arm_blocks = false | ||
imports_granularity = "Crate" | ||
newline_style = "Unix" | ||
normalize_comments = true | ||
normalize_doc_attributes = true | ||
reorder_impl_items = true | ||
group_imports = "StdExternalCrate" | ||
reorder_modules = true | ||
report_fixme = "Always" | ||
# report_todo = "Always" | ||
struct_field_align_threshold = 20 | ||
struct_lit_single_line = false | ||
tab_spaces = 2 | ||
use_field_init_shorthand = true | ||
use_try_shorthand = true | ||
where_single_line = true | ||
wrap_comments = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
// This file is part of Germ <https://github.com/gemrest/sydney>. | ||
// | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, version 3. | ||
// | ||
// This program is distributed in the hope that it will be useful, but | ||
// WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
// General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
// | ||
// Copyright (C) 2022-2022 Fuwn <[email protected]> | ||
// SPDX-License-Identifier: GPL-3.0-only | ||
|
||
use std::time::{Duration, Instant}; | ||
|
||
use crossterm::event; | ||
use germ::request::Status; | ||
use url::Url; | ||
|
||
use crate::{input::Mode as InputMode, stateful_list::StatefulList}; | ||
|
||
pub struct App { | ||
pub items: StatefulList<(Vec<String>, Option<String>)>, | ||
pub input: String, | ||
pub input_mode: InputMode, | ||
pub command_stroke_history: Vec<event::KeyCode>, | ||
pub command_history: Vec<String>, | ||
pub command_history_cursor: usize, | ||
pub error: Option<String>, | ||
pub url: Url, | ||
pub capsule_history: Vec<Url>, | ||
pub previous_capsule: Option<Url>, | ||
pub response_input: String, | ||
pub accept_response_input: bool, | ||
pub response_input_text: String, | ||
pub wrap_at: usize, | ||
} | ||
impl App { | ||
pub fn new() -> Self { | ||
let url = Url::parse("gemini://gemini.circumlunar.space/").unwrap(); | ||
|
||
let mut app = Self { | ||
response_input: String::new(), | ||
error: None, | ||
command_stroke_history: Vec::new(), | ||
input: String::new(), | ||
input_mode: InputMode::Normal, | ||
items: StatefulList::with_items(Vec::new()), | ||
command_history: vec![], | ||
command_history_cursor: 0, | ||
url, | ||
capsule_history: vec![], | ||
previous_capsule: None, | ||
accept_response_input: false, | ||
response_input_text: "".to_string(), | ||
wrap_at: 80, | ||
}; | ||
|
||
app.make_request(); | ||
|
||
app | ||
} | ||
|
||
pub fn set_url(&mut self, url: Url) { | ||
self.previous_capsule = Some(self.url.clone()); | ||
self.url = url; | ||
} | ||
|
||
pub fn make_request(&mut self) { | ||
self.items = StatefulList::with_items({ | ||
let mut items = vec![]; | ||
|
||
match germ::request::request(&self.url) { | ||
Ok(mut response) => { | ||
if response.status() == &Status::TemporaryRedirect | ||
|| response.status() == &Status::PermanentRedirect | ||
{ | ||
self.url = Url::parse(&if response.meta().starts_with('/') { | ||
format!( | ||
"gemini://{}{}", | ||
self.url.host_str().unwrap(), | ||
response.meta() | ||
) | ||
} else if response.meta().starts_with("gemini://") { | ||
response.meta().to_string() | ||
} else if !response.meta().starts_with('/') | ||
&& !response.meta().starts_with("gemini://") | ||
{ | ||
format!( | ||
"{}/{}", | ||
self.url.to_string().trim_end_matches('/'), | ||
response.meta() | ||
) | ||
} else { | ||
self.url.to_string() | ||
}) | ||
.unwrap(); | ||
|
||
response = germ::request::request(&self.url).unwrap(); | ||
} | ||
|
||
if response.status() == &Status::Input | ||
|| response.status() == &Status::SensitiveInput | ||
{ | ||
self.accept_response_input = true; | ||
self.response_input_text = response.meta().to_string(); | ||
} | ||
|
||
if let Some(content) = response.content().clone() { | ||
for line in content.lines().clone() { | ||
let line = line.replace('\t', " "); | ||
let mut parts = line.split_whitespace(); | ||
let lines = if line.is_empty() { | ||
vec![line.to_string()] | ||
} else { | ||
line | ||
.as_bytes() | ||
.chunks(self.wrap_at) | ||
.map(|buf| { | ||
#[allow(unsafe_code)] | ||
unsafe { std::str::from_utf8_unchecked(buf) }.to_string() | ||
}) | ||
.collect::<Vec<_>>() | ||
}; | ||
|
||
if let (Some("=>"), Some(to)) = (parts.next(), parts.next()) { | ||
items.push((lines, Some(to.to_string()))); | ||
} else { | ||
items.push((lines, None)); | ||
} | ||
} | ||
} else if response.status() != &Status::Input | ||
&& response.status() != &Status::SensitiveInput | ||
{ | ||
items.push((vec![response.meta().to_string()], None)); | ||
|
||
self.error = Some(response.meta().to_string()); | ||
} | ||
|
||
if let Some(last_url) = self.capsule_history.last() { | ||
if last_url.to_string() != self.url.to_string() { | ||
self.capsule_history.push(self.url.clone()); | ||
} | ||
} else { | ||
self.capsule_history.push(self.url.clone()); | ||
} | ||
} | ||
Err(error) => { | ||
self.error = Some(error.to_string()); | ||
|
||
return; | ||
} | ||
} | ||
|
||
items | ||
}); | ||
} | ||
|
||
pub fn run<B: tui::backend::Backend>( | ||
terminal: &mut tui::Terminal<B>, | ||
mut app: Self, | ||
tick_rate: Duration, | ||
) -> std::io::Result<()> { | ||
let mut last_tick = Instant::now(); | ||
loop { | ||
terminal.draw(|f| crate::ui::ui(f, &mut app))?; | ||
|
||
let timeout = tick_rate | ||
.checked_sub(last_tick.elapsed()) | ||
.unwrap_or_else(|| Duration::from_secs(0)); | ||
|
||
if event::poll(timeout)? { | ||
if let event::Event::Key(key) = event::read()? { | ||
if crate::input::handle_key_strokes(&mut app, key) { | ||
return Ok(()); | ||
} | ||
} | ||
} | ||
|
||
if last_tick.elapsed() >= tick_rate { | ||
last_tick = Instant::now(); | ||
} | ||
} | ||
} | ||
|
||
pub fn go_back(&mut self) { | ||
if let Some(url) = self.capsule_history.pop() { | ||
if url == self.url { | ||
if let Some(url) = self.capsule_history.pop() { | ||
self.set_url(url); | ||
} | ||
} else { | ||
self.set_url(url); | ||
} | ||
|
||
self.make_request(); | ||
} | ||
} | ||
} |
Oops, something went wrong.