Remember the TUI server selection
This commit is contained in:
@@ -75,6 +75,10 @@ representative repositories.
|
|||||||
- [ ] Server profiles can be added, authenticated, selected, edited, renamed,
|
- [ ] Server profiles can be added, authenticated, selected, edited, renamed,
|
||||||
and deleted. Blank token on edit preserves the existing token; removing
|
and deleted. Blank token on edit preserves the existing token; removing
|
||||||
the last server leaves the server manager available.
|
the last server leaves the server manager available.
|
||||||
|
- [ ] Select a different server, quit, and relaunch without `--server`; the TUI
|
||||||
|
restores that server. Relaunch with `--server NAME`; the explicit server
|
||||||
|
wins and becomes the restored server after a clean exit. Renaming or
|
||||||
|
deleting the remembered profile leaves a valid selection.
|
||||||
- [ ] Editors move between fields with Tab/Shift-Tab, support cursor movement,
|
- [ ] Editors move between fields with Tab/Shift-Tab, support cursor movement,
|
||||||
Unicode insertion, Delete, and Backspace without navigating back, mask
|
Unicode insertion, Delete, and Backspace without navigating back, mask
|
||||||
tokens, save with Ctrl-S, and cancel with Esc.
|
tokens, save with Ctrl-S, and cancel with Esc.
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ pub struct TuiPreferences {
|
|||||||
pub refresh_seconds: u64,
|
pub refresh_seconds: u64,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub favorites: BTreeSet<String>,
|
pub favorites: BTreeSet<String>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub last_server: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TuiPreferences {
|
impl Default for TuiPreferences {
|
||||||
@@ -37,6 +39,7 @@ impl Default for TuiPreferences {
|
|||||||
Self {
|
Self {
|
||||||
refresh_seconds: default_refresh_seconds(),
|
refresh_seconds: default_refresh_seconds(),
|
||||||
favorites: BTreeSet::new(),
|
favorites: BTreeSet::new(),
|
||||||
|
last_server: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,6 +153,9 @@ impl Config {
|
|||||||
if old_url != new_url {
|
if old_url != new_url {
|
||||||
migrate_favorites(&mut updated.tui.favorites, &old_url, new_url);
|
migrate_favorites(&mut updated.tui.favorites, &old_url, new_url);
|
||||||
}
|
}
|
||||||
|
if updated.tui.last_server.as_deref() == Some(original_name) {
|
||||||
|
updated.tui.last_server = Some(name.into());
|
||||||
|
}
|
||||||
updated.save()?;
|
updated.save()?;
|
||||||
*self = updated;
|
*self = updated;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -169,6 +175,9 @@ impl Config {
|
|||||||
{
|
{
|
||||||
remove_favorites(&mut updated.tui.favorites, &removed_url);
|
remove_favorites(&mut updated.tui.favorites, &removed_url);
|
||||||
}
|
}
|
||||||
|
if updated.tui.last_server.as_deref() == Some(name) {
|
||||||
|
updated.tui.last_server = updated.servers.keys().next().cloned();
|
||||||
|
}
|
||||||
updated.save()?;
|
updated.save()?;
|
||||||
*self = updated;
|
*self = updated;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -182,6 +191,22 @@ impl Config {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_tui_last_server(&mut self, name: Option<&str>) -> Result<()> {
|
||||||
|
if let Some(name) = name
|
||||||
|
&& !self.servers.contains_key(name)
|
||||||
|
{
|
||||||
|
return Err(format!("server profile {name:?} does not exist"));
|
||||||
|
}
|
||||||
|
if self.tui.last_server.as_deref() == name {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let mut updated = self.clone();
|
||||||
|
updated.tui.last_server = name.map(str::to_owned);
|
||||||
|
updated.save()?;
|
||||||
|
*self = updated;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_tui_favorite(&self, pane: &str, server_url: &str, repository: &RepositoryId) -> bool {
|
pub fn is_tui_favorite(&self, pane: &str, server_url: &str, repository: &RepositoryId) -> bool {
|
||||||
self.tui
|
self.tui
|
||||||
.favorites
|
.favorites
|
||||||
@@ -455,9 +480,11 @@ mod tests {
|
|||||||
.login("code.example", "secret", Provider::Forgejo)
|
.login("code.example", "secret", Provider::Forgejo)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
config.set_tui_refresh_seconds(9).unwrap();
|
config.set_tui_refresh_seconds(9).unwrap();
|
||||||
|
config.set_tui_last_server(Some("code.example")).unwrap();
|
||||||
|
|
||||||
let mut loaded = Config::load_from(path).unwrap();
|
let mut loaded = Config::load_from(path).unwrap();
|
||||||
assert_eq!(loaded.tui.refresh_seconds, 9);
|
assert_eq!(loaded.tui.refresh_seconds, 9);
|
||||||
|
assert_eq!(loaded.tui.last_server.as_deref(), Some("code.example"));
|
||||||
assert_eq!(loaded.servers["code.example"].token, "secret");
|
assert_eq!(loaded.servers["code.example"].token, "secret");
|
||||||
assert_eq!(loaded.servers["code.example"].provider, Provider::Forgejo);
|
assert_eq!(loaded.servers["code.example"].provider, Provider::Forgejo);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -484,6 +511,7 @@ mod tests {
|
|||||||
assert!(loaded.is_tui_favorite("issues", "https://new.example", &repository));
|
assert!(loaded.is_tui_favorite("issues", "https://new.example", &repository));
|
||||||
assert!(!loaded.servers.contains_key("code.example"));
|
assert!(!loaded.servers.contains_key("code.example"));
|
||||||
assert_eq!(loaded.servers["new.example"].token, "new-secret");
|
assert_eq!(loaded.servers["new.example"].token, "new-secret");
|
||||||
|
assert_eq!(loaded.tui.last_server.as_deref(), Some("new.example"));
|
||||||
assert!(
|
assert!(
|
||||||
fs::read_to_string(&loaded.path)
|
fs::read_to_string(&loaded.path)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -506,6 +534,8 @@ mod tests {
|
|||||||
fs::metadata(&loaded.path).unwrap().permissions().mode() & 0o777,
|
fs::metadata(&loaded.path).unwrap().permissions().mode() & 0o777,
|
||||||
0o600
|
0o600
|
||||||
);
|
);
|
||||||
|
loaded.logout("new.example").unwrap();
|
||||||
|
assert_eq!(loaded.tui.last_server, None);
|
||||||
|
|
||||||
fs::remove_dir_all(directory).unwrap();
|
fs::remove_dir_all(directory).unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,6 +257,15 @@ impl App {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn persist_selected_server(&mut self) -> Result<(), String> {
|
||||||
|
let selected = self
|
||||||
|
.config
|
||||||
|
.servers
|
||||||
|
.contains_key(&self.server_name)
|
||||||
|
.then_some(self.server_name.as_str());
|
||||||
|
self.config.set_tui_last_server(selected)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn handle_key(&mut self, key: KeyEvent) {
|
pub async fn handle_key(&mut self, key: KeyEvent) {
|
||||||
self.last_input = Instant::now();
|
self.last_input = Instant::now();
|
||||||
self.status_deadline = None;
|
self.status_deadline = None;
|
||||||
@@ -1675,6 +1684,14 @@ fn initial_selection(config: &Config, requested: Option<&str>) -> Result<Selecti
|
|||||||
if let Some(name) = requested {
|
if let Some(name) = requested {
|
||||||
return config.select(Some(name), None);
|
return config.select(Some(name), None);
|
||||||
}
|
}
|
||||||
|
if let Some(name) = config
|
||||||
|
.tui
|
||||||
|
.last_server
|
||||||
|
.as_deref()
|
||||||
|
.filter(|name| config.servers.contains_key(*name))
|
||||||
|
{
|
||||||
|
return config.select(Some(name), None);
|
||||||
|
}
|
||||||
config.select(None, None).or_else(|_| {
|
config.select(None, None).or_else(|_| {
|
||||||
let name = config
|
let name = config
|
||||||
.servers
|
.servers
|
||||||
@@ -1929,6 +1946,30 @@ fn clock() -> String {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
fn config_with_servers() -> Config {
|
||||||
|
let mut config = Config::default();
|
||||||
|
config.servers = [
|
||||||
|
(
|
||||||
|
"first.example".into(),
|
||||||
|
gotcha_gitea::ServerProfile {
|
||||||
|
url: "https://first.example".into(),
|
||||||
|
token: "first".into(),
|
||||||
|
provider: Provider::Gitea,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"second.example".into(),
|
||||||
|
gotcha_gitea::ServerProfile {
|
||||||
|
url: "https://second.example".into(),
|
||||||
|
token: "second".into(),
|
||||||
|
provider: Provider::Forgejo,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into();
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn changed_files_open_only_their_own_diff() {
|
fn changed_files_open_only_their_own_diff() {
|
||||||
let diff = "diff --git a/one.txt b/one.txt\n--- a/one.txt\n+++ b/one.txt\n+one\ndiff --git a/two.txt b/two.txt\n--- a/two.txt\n+++ b/two.txt\n+two\n";
|
let diff = "diff --git a/one.txt b/one.txt\n--- a/one.txt\n+++ b/one.txt\n+one\ndiff --git a/two.txt b/two.txt\n--- a/two.txt\n+++ b/two.txt\n+two\n";
|
||||||
@@ -1941,6 +1982,29 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn startup_prefers_the_last_server_unless_explicitly_overridden() {
|
||||||
|
let mut config = config_with_servers();
|
||||||
|
config.tui.last_server = Some("second.example".into());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
initial_selection(&config, None).unwrap().name.as_deref(),
|
||||||
|
Some("second.example")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
initial_selection(&config, Some("first.example"))
|
||||||
|
.unwrap()
|
||||||
|
.name
|
||||||
|
.as_deref(),
|
||||||
|
Some("first.example")
|
||||||
|
);
|
||||||
|
config.tui.last_server = Some("removed.example".into());
|
||||||
|
assert_eq!(
|
||||||
|
initial_selection(&config, None).unwrap().name.as_deref(),
|
||||||
|
Some("first.example")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_clicks_follow_the_rendered_offset_and_rows() {
|
fn list_clicks_follow_the_rendered_offset_and_rows() {
|
||||||
let area = ratatui::layout::Rect::new(10, 4, 30, 10);
|
let area = ratatui::layout::Rect::new(10, 4, 30, 10);
|
||||||
|
|||||||
@@ -44,7 +44,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
let result = run(&mut terminal, &mut app).await;
|
let result = run(&mut terminal, &mut app).await;
|
||||||
execute!(io::stdout(), DisableMouseCapture)?;
|
execute!(io::stdout(), DisableMouseCapture)?;
|
||||||
ratatui::restore();
|
ratatui::restore();
|
||||||
result
|
result?;
|
||||||
|
app.persist_selected_server()?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(
|
async fn run(
|
||||||
|
|||||||
Reference in New Issue
Block a user