Skip to content

Commit

Permalink
feat: initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
Fuwn committed Jul 16, 2022
0 parents commit 5149031
Show file tree
Hide file tree
Showing 15 changed files with 1,707 additions and 0 deletions.
35 changes: 35 additions & 0 deletions .github/workflows/rust.yaml
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Rust
target
Cargo.lock

# CLion
.idea
16 changes: 16 additions & 0 deletions .license_template
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
19 changes: 19 additions & 0 deletions Cargo.toml
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
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions Makefile.toml
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
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Sydney

[![crates.io](https://img.shields.io/crates/v/sydney.svg)](https://crates.io/crates/sydney)
[![github.com](https://github.com/gemrest/sydney/actions/workflows/rust.yaml/badge.svg?branch=main)](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).
2 changes: 2 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[toolchain]
channel = "1.59.0"
30 changes: 30 additions & 0 deletions rustfmt.toml
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
203 changes: 203 additions & 0 deletions src/app.rs
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();
}
}
}
Loading

0 comments on commit 5149031

Please sign in to comment.