diff options
Diffstat (limited to 'pkgs/by-name/ts/tskm/src')
| -rw-r--r-- | pkgs/by-name/ts/tskm/src/browser/mod.rs | 56 | ||||
| -rw-r--r-- | pkgs/by-name/ts/tskm/src/cli.rs | 10 | ||||
| -rw-r--r-- | pkgs/by-name/ts/tskm/src/interface/input/handle.rs | 9 | ||||
| -rw-r--r-- | pkgs/by-name/ts/tskm/src/interface/open/handle.rs | 22 | ||||
| -rw-r--r-- | pkgs/by-name/ts/tskm/src/interface/open/mod.rs | 38 |
5 files changed, 101 insertions, 34 deletions
diff --git a/pkgs/by-name/ts/tskm/src/browser/mod.rs b/pkgs/by-name/ts/tskm/src/browser/mod.rs index 8dd52663..2129982f 100644 --- a/pkgs/by-name/ts/tskm/src/browser/mod.rs +++ b/pkgs/by-name/ts/tskm/src/browser/mod.rs @@ -1,5 +1,5 @@ use std::{ - env, + env, fs, io::Write, os::unix::net::UnixStream, path::PathBuf, @@ -14,11 +14,14 @@ use url::Url; use crate::{state::State, task}; #[allow(clippy::too_many_lines)] -pub fn open_in_browser( +pub fn open_in_browser<U>( selected_project: &task::Project, state: &mut State, - url: Option<Url>, -) -> Result<()> { + urls: Option<Vec<U>>, +) -> Result<()> +where + U: Into<Url>, +{ let old_project: Option<task::Project> = task::Project::get_current().context("Failed to get currently active project")?; let old_task: Option<task::Task> = @@ -101,20 +104,44 @@ pub fn open_in_browser( ) })); - if ipc_socket_path.exists() { - let mut stream = UnixStream::connect(ipc_socket_path)?; + let socket = if ipc_socket_path.exists() { + match UnixStream::connect(&ipc_socket_path) { + Ok(ok) => Some(ok), + Err(err) => match err.kind() { + std::io::ErrorKind::ConnectionRefused => { + // There is no qutebrowser listening to our connection. + fs::remove_file(&ipc_socket_path).with_context(|| { + format!( + "Failed to remove orphaned qutebrowser socket: {}", + ipc_socket_path.display() + ) + })?; + None + } + _ => Err(err).with_context(|| { + format!( + "Failed to connect to qutebrowser's ipc socket at: {}", + ipc_socket_path.display() + ) + })?, + }, + } + } else { + None + }; - let real_url = if let Some(url) = url { - url.to_string() + if let Some(mut stream) = socket { + let real_url = if let Some(urls) = urls { + urls.into_iter().map(|url| url.into().to_string()).collect() } else { // Always add a new tab, so that qutebrowser is marked as “urgent”. - "qute://start".to_owned() + vec!["qute://start".to_owned()] }; stream.write_all( json! { { - "args": [real_url], + "args": real_url, "target_arg": null, "version": "1.0.4", "protocol_version": 1, @@ -128,10 +155,13 @@ pub fn open_in_browser( ExitStatus::default() } else { - let args = if let Some(url) = url { - &[url.to_string()][..] + let args = if let Some(urls) = urls { + urls.into_iter() + .map(Into::<Url>::into) + .map(|u| u.to_string()) + .collect() } else { - &[][..] + vec![] }; process::Command::new(format!( diff --git a/pkgs/by-name/ts/tskm/src/cli.rs b/pkgs/by-name/ts/tskm/src/cli.rs index 90d6023b..359c1050 100644 --- a/pkgs/by-name/ts/tskm/src/cli.rs +++ b/pkgs/by-name/ts/tskm/src/cli.rs @@ -13,11 +13,11 @@ use std::{ffi::OsStr, path::PathBuf}; use anyhow::{bail, Result}; use clap::{builder::StyledStr, ArgAction, Parser, Subcommand, ValueEnum}; use clap_complete::{ArgValueCompleter, CompletionCandidate}; -use url::Url; use crate::{ interface::{ input::{Input, Tag}, + open::UrlLike, project::ProjectName, }, state, task, @@ -126,8 +126,8 @@ pub enum OpenCommand { #[arg(value_parser = task::Project::from_project_string, add = ArgValueCompleter::new(complete_project))] project: task::Project, - /// The URL to open. - url: Option<Url>, + /// The URLs to open. + urls: Option<Vec<UrlLike>>, }, /// Open a selected project in it's Qutebrowser profile. @@ -135,8 +135,8 @@ pub enum OpenCommand { /// This will use rofi's dmenu mode to select one project from the list of all registered /// projects. Select { - /// The URL to open. - url: Option<Url>, + /// The URLs to open. + urls: Option<Vec<UrlLike>>, }, /// List all open tabs in the project. diff --git a/pkgs/by-name/ts/tskm/src/interface/input/handle.rs b/pkgs/by-name/ts/tskm/src/interface/input/handle.rs index 11304633..76eea6dc 100644 --- a/pkgs/by-name/ts/tskm/src/interface/input/handle.rs +++ b/pkgs/by-name/ts/tskm/src/interface/input/handle.rs @@ -69,10 +69,11 @@ pub fn handle(command: InputCommand, state: &mut State) -> Result<()> { 'outer: for all in Input::all()?.chunks(100) { info!("Starting review for the first hundred URLs."); - for input in all { - info!("-> '{input}'"); - open_in_browser(&project, state, Some(input.url.clone()))?; - } + open_in_browser( + &project, + state, + Some(all.iter().map(|f| f.url.clone()).collect()), + )?; { use std::io::{stdin, stdout, Write}; diff --git a/pkgs/by-name/ts/tskm/src/interface/open/handle.rs b/pkgs/by-name/ts/tskm/src/interface/open/handle.rs index ca54b422..3897a63b 100644 --- a/pkgs/by-name/ts/tskm/src/interface/open/handle.rs +++ b/pkgs/by-name/ts/tskm/src/interface/open/handle.rs @@ -8,6 +8,8 @@ // You should have received a copy of the License along with this program. // If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. +use std::str::FromStr; + use anyhow::{bail, Context, Result}; use log::{error, info}; use url::Url; @@ -17,7 +19,15 @@ use crate::{browser::open_in_browser, cli::OpenCommand, rofi, state::State, task fn is_empty(project: &task::Project) -> Result<bool> { let tabs = get_tabs(project)?; - Ok(tabs.is_empty()) + if tabs.is_empty() { + Ok(true) + } else if tabs.len() > 1 { + Ok(false) + } else { + let url = &tabs[0].1; + + Ok(url == &Url::from_str("qute://start/").expect("Hardcoded")) + } } #[allow(clippy::too_many_lines)] @@ -33,7 +43,7 @@ pub fn handle(command: OpenCommand, state: &mut State) -> Result<()> { project.to_project_display(), if is_empty { "is empty" } else { "is not empty" } ); - open_in_browser(project, state, None).with_context(|| { + open_in_browser(project, state, None::<Vec<Url>>).with_context(|| { format!( "Failed to open project ('{}') in qutebrowser", project.to_project_display() @@ -51,13 +61,13 @@ pub fn handle(command: OpenCommand, state: &mut State) -> Result<()> { } } } - OpenCommand::Project { project, url } => { + OpenCommand::Project { project, urls } => { project.touch().context("Failed to touch project")?; - open_in_browser(&project, state, url).with_context(|| { + open_in_browser(&project, state, urls).with_context(|| { format!("Failed to open project: {}", project.to_project_display()) })?; } - OpenCommand::Select { url } => { + OpenCommand::Select { urls } => { let selected_project: task::Project = task::Project::from_project_string( &rofi::select( task::Project::all() @@ -75,7 +85,7 @@ pub fn handle(command: OpenCommand, state: &mut State) -> Result<()> { .touch() .context("Failed to touch project")?; - open_in_browser(&selected_project, state, url).context("Failed to open project")?; + open_in_browser(&selected_project, state, urls).context("Failed to open project")?; } OpenCommand::ListTabs { projects, mode } => { let projects = { diff --git a/pkgs/by-name/ts/tskm/src/interface/open/mod.rs b/pkgs/by-name/ts/tskm/src/interface/open/mod.rs index 40e057c1..e403b4a8 100644 --- a/pkgs/by-name/ts/tskm/src/interface/open/mod.rs +++ b/pkgs/by-name/ts/tskm/src/interface/open/mod.rs @@ -8,7 +8,11 @@ // You should have received a copy of the License along with this program. // If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. -use std::{fs::File, io::Read, str::FromStr}; +use std::{ + fs::{self, File}, + io::Read, + str::FromStr, +}; use anyhow::{anyhow, Context, Result}; use taskchampion::chrono::NaiveDateTime; @@ -20,15 +24,37 @@ use crate::task::Project; pub mod handle; pub use handle::handle; +/// An Url that also accepts file paths +#[derive(Debug, Clone)] +pub struct UrlLike(Url); + +impl FromStr for UrlLike { + type Err = url::ParseError; + + fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { + if let Ok(u) = fs::canonicalize(s) { + Ok(Self(Url::from_file_path(u).expect( + "The path could be canonicalized, as such it is valid for this", + ))) + } else { + Url::from_str(s).map(Self) + } + } +} + +impl From<UrlLike> for Url { + fn from(value: UrlLike) -> Self { + value.0 + } +} + impl Project { pub(super) fn get_sessionstore(&self) -> Result<SessionStore> { let path = dirs::data_local_dir() .context("Failed to get data dir")? .join("qutebrowser") .join(self.to_project_display()) - // NOTE(@bpeetz): We could use another real session name, but this file should - // always exist. <2025-06-03> - .join("data/sessions/_autosave.yml"); + .join("data/sessions/default.yml"); let mut file = File::open(&path) .with_context(|| format!("Failed to open path '{}'", path.display()))?; @@ -92,7 +118,7 @@ fn qute_store_from_yaml(yaml: &[Yaml]) -> Result<SessionStore> { Ok::<_, anyhow::Error>(TabHistory { active: hash .get(&Yaml::String("active".to_owned())) - .ok_or(anyhow!("Missing tab history active"))? + .unwrap_or(&Yaml::Boolean(false)) .as_bool() .ok_or(anyhow!("tab history active not bool"))?, last_visited: NaiveDateTime::from_str( @@ -126,7 +152,7 @@ fn qute_store_from_yaml(yaml: &[Yaml]) -> Result<SessionStore> { .context("Failed to parse url")?, zoom: hash .get(&Yaml::String("zoom".to_owned())) - .ok_or(anyhow!("Missing tab history zoom"))? + .unwrap_or(&Yaml::Real("1.0".to_owned())) .as_f64() .ok_or(anyhow!("tab history zoom not 64"))?, }) |
