дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 19:25:54 +02:00
- ide-web: built-in static server on 127.0.0.1 that serves project files, plus detection and launching of installed browsers. - Open In › Browser in the editor, project view and tab menus; browser bar and built-in preview beside HTML editors; ⌃⇧R opens the default browser. - Emmet: Tab expansion in markup editors and Update Tag with Emmet. Claude-Session: https://claude.ai/code/session_01SaPPsVPuk6ZTNsoUCNCbMv
130 рядки
4.5 KiB
Rust
130 рядки
4.5 KiB
Rust
use ide_core::{IdeError, IdeResult};
|
|
use serde::{Deserialize, Serialize};
|
|
use specta::Type;
|
|
use std::path::PathBuf;
|
|
|
|
/// An installed browser.
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Type)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Browser {
|
|
/// `chrome`, `firefox`, `safari`, `edge`, `brave`, `chromium`.
|
|
pub id: String,
|
|
pub name: String,
|
|
}
|
|
|
|
struct Known {
|
|
id: &'static str,
|
|
name: &'static str,
|
|
/// macOS application bundle names.
|
|
mac: &'static [&'static str],
|
|
/// Windows executables relative to Program Files / Local AppData.
|
|
windows: &'static [&'static str],
|
|
/// Linux commands on `PATH`.
|
|
linux: &'static [&'static str],
|
|
}
|
|
|
|
const KNOWN: &[Known] = &[
|
|
Known {
|
|
id: "chrome",
|
|
name: "Chrome",
|
|
mac: &["Google Chrome"],
|
|
windows: &[r"Google\Chrome\Application\chrome.exe"],
|
|
linux: &["google-chrome", "google-chrome-stable"],
|
|
},
|
|
Known {
|
|
id: "firefox",
|
|
name: "Firefox",
|
|
mac: &["Firefox", "Firefox Developer Edition"],
|
|
windows: &[r"Mozilla Firefox\firefox.exe"],
|
|
linux: &["firefox"],
|
|
},
|
|
Known { id: "safari", name: "Safari", mac: &["Safari"], windows: &[], linux: &[] },
|
|
Known {
|
|
id: "edge",
|
|
name: "Edge",
|
|
mac: &["Microsoft Edge"],
|
|
windows: &[r"Microsoft\Edge\Application\msedge.exe"],
|
|
linux: &["microsoft-edge", "microsoft-edge-stable"],
|
|
},
|
|
Known {
|
|
id: "brave",
|
|
name: "Brave",
|
|
mac: &["Brave Browser"],
|
|
windows: &[r"BraveSoftware\Brave-Browser\Application\brave.exe"],
|
|
linux: &["brave-browser", "brave"],
|
|
},
|
|
Known {
|
|
id: "chromium",
|
|
name: "Chromium",
|
|
mac: &["Chromium"],
|
|
windows: &[r"Chromium\Application\chrome.exe"],
|
|
linux: &["chromium", "chromium-browser"],
|
|
},
|
|
];
|
|
|
|
/// Where browser `k` is installed, if it is.
|
|
fn locate(k: &Known) -> Option<PathBuf> {
|
|
if cfg!(target_os = "macos") {
|
|
let mut dirs = vec![PathBuf::from("/Applications")];
|
|
if let Some(home) = std::env::var_os("HOME") {
|
|
dirs.push(PathBuf::from(home).join("Applications"));
|
|
}
|
|
// Safari moved into the cryptex; /Applications keeps a link to it.
|
|
dirs.push(PathBuf::from("/System/Volumes/Preboot/Cryptexes/App/System/Applications"));
|
|
k.mac.iter().flat_map(|app| dirs.iter().map(move |d| d.join(format!("{app}.app")))).find(|p| p.exists())
|
|
} else if cfg!(windows) {
|
|
let bases: Vec<PathBuf> = ["ProgramFiles", "ProgramFiles(x86)", "LOCALAPPDATA"]
|
|
.iter()
|
|
.filter_map(std::env::var_os)
|
|
.map(PathBuf::from)
|
|
.collect();
|
|
k.windows.iter().flat_map(|exe| bases.iter().map(move |b| b.join(exe))).find(|p| p.is_file())
|
|
} else {
|
|
let path = std::env::var_os("PATH")?;
|
|
let dirs: Vec<PathBuf> = std::env::split_paths(&path).collect();
|
|
k.linux.iter().flat_map(|cmd| dirs.iter().map(move |d| d.join(cmd))).find(|p| p.is_file())
|
|
}
|
|
}
|
|
|
|
/// The known browsers installed on this machine, in a fixed order.
|
|
pub fn installed() -> Vec<Browser> {
|
|
KNOWN.iter().filter(|k| locate(k).is_some()).map(|k| Browser { id: k.id.into(), name: k.name.into() }).collect()
|
|
}
|
|
|
|
/// Opens `url` in browser `id`, or in the system default browser.
|
|
pub fn open(url: &str, id: Option<&str>) -> IdeResult<()> {
|
|
if !(url.starts_with("http://") || url.starts_with("https://") || url.starts_with("file://")) {
|
|
return Err(IdeError::invalid(format!("not a web URL: {url}")));
|
|
}
|
|
let result = match id {
|
|
None => open::that_detached(url),
|
|
Some(id) => {
|
|
let known =
|
|
KNOWN.iter().find(|k| k.id == id).ok_or_else(|| IdeError::invalid(format!("unknown browser: {id}")))?;
|
|
let app = locate(known).ok_or_else(|| IdeError::unavailable(format!("{} is not installed", known.name)))?;
|
|
open::with_detached(url, app.to_string_lossy().as_ref())
|
|
}
|
|
};
|
|
result.map_err(|e| IdeError::unavailable(format!("cannot open the browser: {e}")))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn ids_are_unique() {
|
|
let mut ids: Vec<_> = KNOWN.iter().map(|k| k.id).collect();
|
|
ids.sort_unstable();
|
|
ids.dedup();
|
|
assert_eq!(ids.len(), KNOWN.len());
|
|
}
|
|
|
|
#[test]
|
|
fn refuses_non_web_urls() {
|
|
assert!(open("/etc/passwd", None).is_err());
|
|
assert!(open("javascript:alert(1)", None).is_err());
|
|
assert!(open("http://127.0.0.1/", Some("netscape")).is_err());
|
|
}
|
|
}
|