diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9cd9295..82009bc 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -113,6 +113,8 @@ jobs: with: name: linux path: ./linux + - name: Set executable + run: chmod +x ./linux/immt - uses: actions/download-artifact@v4 with: name: windows @@ -121,6 +123,8 @@ jobs: with: name: mac_aarch64 path: ./mac + - name: Set executable + run: chmod +x ./mac/immt - uses: actions/download-artifact@v4 with: name: ts diff --git a/Cargo.toml b/Cargo.toml index 7143455..961527f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,6 +125,9 @@ html5ever = "0.29.0" async-lsp = {version="0.2",features=["tokio","async-io"]} futures = {version="0.3"} +image = { version = "0.25"} +webp = { version= "0.3"} +dashmap = { version = "5" } # leptos leptos = { version = "=0.7.0-rc1", features=["nightly"]} @@ -158,7 +161,7 @@ console_error_panic_hook = "0.1.7" reqwasm = "0.5" #reqwest = {version="0.12",features=["json"]} gloo-storage = "0.3" -web-sys = { version = "0.3.70" } +web-sys = { version = "0.3" } tsify-next = {version="0.5", features = ["js"]} js-sys = "0.3" diff --git a/source/lsp/src/documents.rs b/source/lsp/src/documents.rs index 3cd07da..a06af40 100644 --- a/source/lsp/src/documents.rs +++ b/source/lsp/src/documents.rs @@ -3,7 +3,7 @@ use std::path::Path; use async_lsp::lsp_types::{Position, Range, Url}; use immt_ontology::uris::{ArchiveURI, DocumentURI, URIRefTrait}; use immt_stex::quickparse::stex::{STeXParseData, STeXParseDataI}; -use immt_system::backend::{archives::Archive, AnyBackend, GlobalBackend}; +use immt_system::backend::{archives::Archive, AnyBackend, Backend, GlobalBackend}; use immt_utils::time::measure; use crate::LSPStore; @@ -30,19 +30,22 @@ impl LSPDocument { #[allow(clippy::borrowed_box)] #[must_use] pub fn new(text:String,lsp_uri:Url) -> Self { - let path = lsp_uri.to_file_path().ok().map(Into::into); + let path:Option> = lsp_uri.to_file_path().ok().map(Into::into); + let default = || { + let path = path.as_ref()?.as_os_str().to_str()?.into(); + Some((ArchiveURI::no_archive(),Some(path))) + }; let ap = path.as_ref().and_then(|path:&Box| - GlobalBackend::get().manager().all_archives().iter().find_map(|a| - if let Archive::Local(a) = a { - if path.starts_with(a.path()) { - let rel_path = path.display().to_string().strip_prefix(&a.source_dir().display().to_string()).map(Into::into); - Some((a.uri().owned(),rel_path)) - } else {None} - } else {None} - )); + GlobalBackend::get().archive_of(path,|a,rp| { + let uri = a.uri().owned(); + let rp = rp.strip_prefix("/source/").map(|r| r.into()); + (uri,rp) + }) + ).or_else(default); let (archive,rel_path) = ap.map_or((None,None),|(a,p)| (Some(a),p)); let r = LSPText { text ,up_to_date:false, html_up_to_date: false }; let doc_uri = archive.as_ref().and_then(|a| rel_path.as_ref().map(|rp:&Box| DocumentURI::from_archive_relpath(a.clone(), rp))); + //tracing::info!("Document: {lsp_uri}\n - {doc_uri:?}\n - [{archive:?}]{{{rel_path:?}}}"); let data = DocumentData { lsp_uri,path,archive,rel_path,doc_uri }; diff --git a/source/lsp/src/implementation.rs b/source/lsp/src/implementation.rs index f70072a..e1165fc 100644 --- a/source/lsp/src/implementation.rs +++ b/source/lsp/src/implementation.rs @@ -168,7 +168,6 @@ impl LanguageServer for ServerWrapper { }; let _ = client.publish_diagnostics(r); })); - //d.with_text(|t| tracing::info!("new text:\n{}",t)); } else { tracing::warn!("document not found: {}",document.uri); } @@ -179,7 +178,7 @@ impl LanguageServer for ServerWrapper { #[allow(clippy::let_underscore_future)] //impl_notification!(! did_save = DidSaveTextDocument); fn did_save(&mut self,params:lsp::DidSaveTextDocumentParams) -> Self::NotifyResult { - tracing::info!("did_save: {}",params.text_document.uri); + tracing::trace!("did_save: {}",params.text_document.uri); let state = self.inner.state().clone(); let client = self.inner.client().clone(); let uri = params.text_document.uri; @@ -294,8 +293,8 @@ impl LanguageServer for ServerWrapper { #[must_use] // impl_request!(! hover = HoverRequest => (None)); fn hover(&mut self, params: lsp::HoverParams) -> Res> { - tracing::info_span!("hover").in_scope(move || { - tracing::info!("uri: {},work_done_progress_params: {:?}, position: {:?}", + tracing::trace_span!("hover").in_scope(move || { + tracing::trace!("uri: {},work_done_progress_params: {:?}, position: {:?}", params.text_document_position_params.text_document.uri, params.work_done_progress_params, params.text_document_position_params.position @@ -391,8 +390,8 @@ impl LanguageServer for ServerWrapper { #[must_use] // impl_request!(semantic_tokens_full = SemanticTokensFullRequest); fn semantic_tokens_full(&mut self, params: lsp::SemanticTokensParams) -> Res> { - tracing::info_span!("semantic_tokens_full").in_scope(|| { - tracing::info!("work_done_progress_params: {:?}, partial_results: {:?}, uri: {}", + tracing::trace_span!("semantic_tokens_full").in_scope(|| { + tracing::trace!("work_done_progress_params: {:?}, partial_results: {:?}, uri: {}", params.work_done_progress_params, params.partial_result_params, params.text_document.uri @@ -407,25 +406,11 @@ impl LanguageServer for ServerWrapper { }) } - #[must_use] - // impl_request!(semantic_tokens_full_delta = SemanticTokensFullDeltaRequest); - fn semantic_tokens_full_delta(&mut self, params: lsp::SemanticTokensDeltaParams) -> Res> { - tracing::info_span!("semantic_tokens_full_delta").in_scope(|| { - tracing::info!("work_done_progress_params: {:?}, partial_results: {:?}, previous_result_id: {:?}, uri:{}", - params.work_done_progress_params, - params.partial_result_params, - params.previous_result_id, - params.text_document.uri - ); - Box::pin(std::future::ready(Ok(None))) - }) - } - #[must_use] // impl_request!(semantic_tokens_range = SemanticTokensRangeRequest); fn semantic_tokens_range(&mut self, params: lsp::SemanticTokensRangeParams) -> Res> { - tracing::info_span!("semantic_tokens_range").in_scope(|| { - tracing::info!("work_done_progress_params: {:?}, partial_results: {:?}, range: {:?}, uri:{}", + tracing::trace_span!("semantic_tokens_range").in_scope(|| { + tracing::trace!("work_done_progress_params: {:?}, partial_results: {:?}, range: {:?}, uri:{}", params.work_done_progress_params, params.partial_result_params, params.range, @@ -441,6 +426,20 @@ impl LanguageServer for ServerWrapper { }) } + #[must_use] + // impl_request!(semantic_tokens_full_delta = SemanticTokensFullDeltaRequest); + fn semantic_tokens_full_delta(&mut self, params: lsp::SemanticTokensDeltaParams) -> Res> { + tracing::info_span!("semantic_tokens_full_delta").in_scope(|| { + tracing::info!("work_done_progress_params: {:?}, partial_results: {:?}, previous_result_id: {:?}, uri:{}", + params.work_done_progress_params, + params.partial_result_params, + params.previous_result_id, + params.text_document.uri + ); + Box::pin(std::future::ready(Ok(None))) + }) + } + // callHierarchy/ impl_request!(incoming_calls = CallHierarchyIncomingCalls); impl_request!(outgoing_calls = CallHierarchyOutgoingCalls); diff --git a/source/lsp/src/state.rs b/source/lsp/src/state.rs index dc4d21e..59dcb74 100644 --- a/source/lsp/src/state.rs +++ b/source/lsp/src/state.rs @@ -205,7 +205,7 @@ impl LSPState { _ => () } } - tracing::info!("document links: {:?}",ret); + //tracing::info!("document links: {:?}",ret); if let Some(p) = progress { p.finish() } ret })) diff --git a/source/main/Cargo.toml b/source/main/Cargo.toml index c73d5c8..c6f1695 100644 --- a/source/main/Cargo.toml +++ b/source/main/Cargo.toml @@ -41,7 +41,11 @@ ssr = [ "dep:immt-lsp", "dep:tracing-subscriber", "dep:tokio-util", - "dep:rayon" + "dep:rayon", + "dep:tex_engine", + "dep:image", + "dep:webp", + "dep:dashmap" ] hydrate = [ "dep:console_error_panic_hook", @@ -87,6 +91,8 @@ immt-system = {workspace=true,features=["tokio"],optional=true} immt-lsp = {workspace=true,optional=true, features=["ws"]} immt-stex = {workspace=true,optional=true} immt-shtml = {workspace=true,optional=true} +tex_engine = {workspace=true,optional=true} + tokio = {workspace=true,features = ["full","rt","rt-multi-thread"],optional=true} toml = {workspace = true,optional=true} color-eyre = {workspace = true,optional=true} @@ -112,6 +118,10 @@ argon2 = {workspace=true,optional=true} tokio-util = {workspace=true,optional=true} tracing-subscriber = {workspace=true,optional=true} +image = {workspace=true,optional=true} +webp = {workspace=true,optional=true} +dashmap = {workspace=true,optional=true} + #hydrate console_error_panic_hook = {workspace = true,optional=true} wasm-bindgen = {workspace = true,optional=true} diff --git a/source/main/src/server/img.rs b/source/main/src/server/img.rs new file mode 100644 index 0000000..6105d6e --- /dev/null +++ b/source/main/src/server/img.rs @@ -0,0 +1,97 @@ +use std::{default, ops::Deref, path::PathBuf, sync::atomic::AtomicU64}; + +use axum::body::Body; +use http::Request; +use immt_ontology::uris::ArchiveId; +use immt_system::{backend::{Backend, GlobalBackend}, settings::Settings}; +use immt_utils::time::Timestamp; +use leptos::svg::Image; +use tower::ServiceExt; +use tower_http::services::{fs::ServeFileSystemResponseBody, ServeFile}; + +use super::ServerState; + +#[derive(Clone,Default)] +pub struct ImageStore(immt_utils::triomphe::Arc); + +#[derive(Default)] +struct ImageStoreI { + map:dashmap::DashMap, + count:AtomicU64 +} + +#[derive(Clone,Debug,Hash,PartialEq,Eq)] +pub enum ImageSpec { + Kpse(Box), + ARp(ArchiveId,Box), + File(Box) +} +impl ImageSpec { + pub fn path(&self) -> Option { + match self { + Self::Kpse(p) => tex_engine::engine::filesystem::kpathsea::KPATHSEA.which(p), + Self::ARp(a,p) => + GlobalBackend::get().with_local_archive(a, |a| + a.map(|a| a.path().join(&**p) ) + ), + Self::File(p) => Some(std::path::PathBuf::from(p.to_string())) + } + } +} + +pub struct ImageData { + img: Box<[u8]>, + timestamp: AtomicU64 +} +impl ImageData { + pub fn update(&self) { + let now = Timestamp::now(); + self.timestamp.store(now.0.get() as _,std::sync::atomic::Ordering::SeqCst); + } + pub fn new(data: &[u8]) -> Self { + Self { + img: data.into(), + timestamp: AtomicU64::new(Timestamp::now().0.get()) + } + } +} + + +pub async fn img_handler( + mut uri: http::Uri, + axum::extract::State(ServerState {images,..}): axum::extract::State, + //request: http::Request, +) -> axum::response::Response { + + let default = || { + let mut resp = axum::response::Response::new(ServeFileSystemResponseBody::default()); + *resp.status_mut() = http::StatusCode::NOT_FOUND; + resp + }; + + let Some(s) = uri.query() else { return default(); }; + + let spec = if let Some(s) = s.strip_prefix("kpse=") { + ImageSpec::Kpse(s.into()) + } else if let Some(f) = s.strip_prefix("file=") { + if Settings::get().lsp { + ImageSpec::File(f.into()) + } else { return default(); } + } + else if let Some(s) = s.strip_prefix("a=") { + let Some((a,rp)) = s.split_once("&rp=") else { return default(); }; + let a = a.parse().unwrap_or_else(|_| unreachable!()); + let rp = rp.into(); + ImageSpec::ARp(a,rp) + } else { return default() }; + + //tracing::info!("HERE: {spec:?}"); + if let Some(p) = spec.path() { + let req = Request::builder() + .uri(uri.clone()) + .body(Body::empty()) + .unwrap(); + ServeFile::new(p).oneshot(req).await.unwrap_or_else(|_| default()) + } else {default() } + +} \ No newline at end of file diff --git a/source/main/src/server/mod.rs b/source/main/src/server/mod.rs index 67422d8..b31466e 100644 --- a/source/main/src/server/mod.rs +++ b/source/main/src/server/mod.rs @@ -1,6 +1,7 @@ pub mod db; pub mod settings; pub mod lsp; +mod img; use std::future::IntoFuture; @@ -59,6 +60,7 @@ pub async fn run(port_channel:Option>>) { routes, axum::routing::get(|a, b, c| routes_handler(a, b, c).in_current_span()), ) + .route("/img",axum::routing::get(img::img_handler)) .fallback(file_and_error_handler) .layer(auth_layer) .layer( @@ -93,7 +95,7 @@ pub async fn run(port_channel:Option>>) { async fn routes_handler( auth_session: axum_login::AuthSession, - extract::State(ServerState { db, options }): extract::State, + extract::State(ServerState { db, options,.. }): extract::State, request: http::Request, ) -> impl IntoResponse { let handler = leptos_axum::render_app_to_stream_with_context( @@ -108,7 +110,7 @@ async fn routes_handler( async fn server_fn_handle( auth_session: axum_login::AuthSession, - extract::State(ServerState { db, options }): extract::State, + extract::State(ServerState { db, options,.. }): extract::State, request: http::Request, ) -> impl IntoResponse { leptos_axum::handle_server_fns_with_context( @@ -154,6 +156,7 @@ impl tower_http::trace::MakeSpan for SpanLayer { pub(crate) struct ServerState { options: LeptosOptions, db: DBBackend, + pub(crate) images: img::ImageStore, } impl ServerState { @@ -163,6 +166,7 @@ impl ServerState { Self { options: leptos_cfg.leptos_options, db, + images: img::ImageStore::default(), } } diff --git a/source/ontology/src/uris/archives.rs b/source/ontology/src/uris/archives.rs index d52bfcd..854f2ea 100644 --- a/source/ontology/src/uris/archives.rs +++ b/source/ontology/src/uris/archives.rs @@ -16,6 +16,9 @@ use triomphe::Arc; lazy_static! { static ref ARCHIVE_IDS: Arc>> = Arc::new(Mutex::new(TArcInterner::default())); + + static ref NO_ARCHIVE_ID:ArchiveId = ArchiveId::new("no/archive"); + static ref NO_ARCHIVE_URI:ArchiveURI = "http://unknown.source?a=no/archive".parse().unwrap_or_else(|_| unreachable!()); } #[derive(Clone, PartialEq, Eq, Hash)] @@ -27,6 +30,9 @@ impl ArchiveId { s.rsplit_once('/').map_or(s, |(_, s)| s) } + #[inline]#[must_use] + pub fn no_archive() -> Self { NO_ARCHIVE_ID.clone() } + #[must_use] pub fn steps(&self) -> std::str::Split { self.as_ref().split('/') @@ -108,6 +114,8 @@ impl Display for ArchiveURI { debugdisplay!(ArchiveURI); impl ArchiveURI { + #[must_use] + pub fn no_archive() -> Self { NO_ARCHIVE_URI.clone() } #[must_use] #[allow(clippy::missing_const_for_fn)] pub fn new(base: BaseURI, archive: ArchiveId) -> Self { diff --git a/source/ontology/src/uris/narrative/documents.rs b/source/ontology/src/uris/narrative/documents.rs index fb58b01..9e3f420 100644 --- a/source/ontology/src/uris/narrative/documents.rs +++ b/source/ontology/src/uris/narrative/documents.rs @@ -9,7 +9,7 @@ use std::fmt::Display; use std::str::{FromStr, Split}; lazy_static::lazy_static! { - static ref NO_DOCUMENT:DocumentURI = "http://unknown.document?a=no/archive&d=unknown_document&l=en".parse().unwrap_or_else(|_| unreachable!()); + static ref NO_DOCUMENT:DocumentURI = "http://unknown.source?a=no/archive&d=unknown_document&l=en".parse().unwrap_or_else(|_| unreachable!()); } #[derive(Clone, PartialEq, Eq, Hash)] @@ -27,7 +27,7 @@ impl DocumentURI { pub fn from_archive_relpath(a:ArchiveURI,rel_path:&str) -> Self { let (path,mut name) = rel_path.rsplit_once('/') .unwrap_or(("",rel_path)); - name = name.rsplit_once('.').unwrap_or_else(|| unreachable!()).0; + name = name.rsplit_once('.').map_or_else(|| name,|(name,_)| name); let lang = Language::from_rel_path(name); name = name.strip_suffix(&format!(".{lang}")).unwrap_or(name); (a % path) & (name,lang) diff --git a/source/shtml/system/Cargo.toml b/source/shtml/system/Cargo.toml index eb13960..d664bb6 100644 --- a/source/shtml/system/Cargo.toml +++ b/source/shtml/system/Cargo.toml @@ -13,6 +13,7 @@ html5ever = {workspace=true} lazy_static = {workspace=true} serde = {workspace=true} bincode = {workspace=true} +tex_engine = {workspace=true} [lints] workspace = true diff --git a/source/shtml/system/src/parser/mod.rs b/source/shtml/system/src/parser/mod.rs index f62546c..4ca9ab5 100644 --- a/source/shtml/system/src/parser/mod.rs +++ b/source/shtml/system/src/parser/mod.rs @@ -3,10 +3,9 @@ pub mod termsnotations; use std::cell::{Cell, RefCell}; -use either::Either; use html5ever::{interface::{NodeOrText, TreeSink}, parse_document, serialize::SerializeOpts, tendril::{SliceExt, StrTendril, TendrilSink}, ParseOpts, QualName}; use immt_ontology::{languages::Language, narration::{documents::UncheckedDocument, LazyDocRef}, triple, uris::{ArchiveId, ArchiveURI, ArchiveURITrait, BaseURI, DocumentURI, ModuleURI, SymbolURI, URIOrRefTrait, URIRefTrait, URIWithLanguage}, DocumentRange}; -use immt_system::{backend::{AnyBackend, Backend}, building::{BuildResult, BuildResultArtifact}, formats::{HTMLData, OMDocResult}}; +use immt_system::{backend::{AnyBackend, Backend}, formats::{HTMLData, OMDocResult}}; use immt_utils::{prelude::HSet, CSS}; use nodes::{ElementData, NodeData, NodeRef}; use shtml_extraction::{errors::SHTMLError, open::{terms::{OpenTerm, VarOrSym}, OpenSHTMLElement}, prelude::{Attributes, ExtractorState, RuleSet, SHTMLElements, SHTMLNode, SHTMLTag, StatefulExtractor}}; @@ -229,6 +228,33 @@ impl TreeSink for HTMLParser<'_> { //assert_eq!(parent.len(),parent.string().len()); match child { NodeOrText::AppendNode(child) => { + if child.as_element().is_some_and(|n| n.name.local.as_ref().eq_ignore_ascii_case("img")) { + let Some(child_elem) = child.as_element() else {unreachable!()}; + let mut attributes = child_elem.attributes.borrow_mut(); + if let Some(src) = attributes.value("src") { + let path = std::path::Path::new(src); + if let Some(newsrc) = self.extractor.borrow().backend.archive_of(path, |a,rp| { + format!("/img?a={}&rp={}",a.id(),&rp[1..]) + }) { + attributes.set("src",&newsrc); + } else { + let kpsewhich = &*tex_engine::engine::filesystem::kpathsea::KPATHSEA; + let last = src.rsplit_once('/').map_or(src,|(_,p)| p); + if let Some(file) = kpsewhich.which(last) { + if file == path { + let file = format!("/img?kpse={last}"); + attributes.set("src",&file); + } + } else { + let file = format!("/img?file={src}"); + attributes.set("src",&file); + } + // TODO + }; + } + drop(attributes); + NodeRef::update_len(child_elem); + } //println!("Current Child: {}: >>>>{}<<<<",child.len(),child.string().replace('\n'," ").replace('\t'," ")); //assert_eq!(child.len(),child.string().len()); if parent.as_document().is_some() { @@ -296,10 +322,10 @@ impl TreeSink for HTMLParser<'_> { #[inline] fn append_based_on_parent_node( - &self, - element: &Self::Handle, - prev_element: &Self::Handle, - child: NodeOrText, + &self, + element: &Self::Handle, + prev_element: &Self::Handle, + child: NodeOrText, ) { if element.parent().is_some() { self.append_before_sibling(element, child); @@ -412,15 +438,22 @@ impl Extractor<'_> { fn split(backend:&AnyBackend,p:&str) -> Option<(ArchiveURI,usize)> { if p.starts_with(META) { return Some((META_URI.clone(),29)) - } else if p == URTHEORIES { + } + if p == URTHEORIES { return Some((UR_URI.clone(),31)) - } else if p == "http://mathhub.info/my/archive" { + } + if p == "http://mathhub.info/my/archive" { return Some((MY_ARCHIVE.clone(),30)) - } else if p == "http://kwarc.info/Papers/stex-mmt/paper" { + } + if p == "http://kwarc.info/Papers/stex-mmt/paper" { return Some((INJECTING.clone(),34)) - } else if p == "http://kwarc.info/Papers/tug/paper" { + } + if p == "http://kwarc.info/Papers/tug/paper" { return Some((TUG.clone(),34)) } + if p.starts_with("file://") { + return Some((ArchiveURI::no_archive(),7)) + } if let Some(mut p) = p.strip_prefix(MATHHUB) { let mut i = MATHHUB.len(); if let Some(s) = p.strip_prefix('/') { @@ -429,8 +462,8 @@ impl Extractor<'_> { } return Self::split_old(backend,p,i) } - backend.with_archive_tree(|tree| - tree.archives.iter().find_map(|a| { + backend.with_archives(|mut tree| + tree.find_map(|a| { let base = a.uri(); let base = base.base().as_ref(); if p.starts_with(base) { @@ -445,8 +478,8 @@ impl Extractor<'_> { } fn split_old(backend:&AnyBackend,p:&str,len:usize) -> Option<(ArchiveURI,usize)> { - backend.with_archive_tree(|tree| - tree.archives.iter().find_map(|a| { + backend.with_archives(|mut tree| + tree.find_map(|a| { if p.starts_with(a.id().as_ref()) { let mut l = a.id().as_ref().len(); let np = &p[l..]; diff --git a/source/system/src/backend/mod.rs b/source/system/src/backend/mod.rs index d2d7f06..ee47b82 100644 --- a/source/system/src/backend/mod.rs +++ b/source/system/src/backend/mod.rs @@ -22,7 +22,7 @@ use immt_utils::{prelude::HMap, triomphe, CSS}; use lazy_static::lazy_static; use parking_lot::RwLock; use rdf::RDFStore; -use std::{ops::Deref, path::PathBuf}; +use std::{ops::Deref, path::{Path, PathBuf}}; use crate::formats::{HTMLData, SourceFormatId}; #[derive(Clone, Debug)] @@ -46,6 +46,8 @@ pub enum AnyBackend{ } pub trait Backend { + type ArchiveIter<'a> : Iterator where Self:Sized; + fn to_any(&self) -> AnyBackend; fn get_document(&self, uri: &DocumentURI) -> Option; fn get_module(&self, uri: &ModuleURI) -> Option; @@ -60,13 +62,29 @@ pub trait Backend { where Self: Sized; - fn with_archive_tree(&self,f:impl FnOnce(&ArchiveTree) -> R) -> R where Self:Sized; + #[allow(irrefutable_let_patterns)] + fn archive_of(&self,p:&Path,mut f:impl FnMut(&LocalArchive,&str) -> R) -> Option where Self:Sized { + let base = p.as_os_str().to_str()?; + self.with_archives(|mut a| a.find_map(|a| { + let Archive::Local(a) = a else {return None}; + let ap = a.path().as_os_str().to_str()?; + base.strip_prefix(ap).map(|rp| f(a,rp)) + })) + } + + fn with_archives(&self,f:impl FnOnce(Self::ArchiveIter<'_>) -> R) -> R where Self:Sized; + + //fn with_archive_tree(&self,f:impl FnOnce(&ArchiveTree) -> R) -> R where Self:Sized; fn submit_triples(&self,in_doc:&DocumentURI,rel_path:&str,iter:impl Iterator) where Self:Sized; fn with_archive(&self, id: &ArchiveId, f: impl FnOnce(Option<&Archive>) -> R) -> R where Self:Sized; + + fn get_html_body(&self, + d:&DocumentURI,full:bool + ) -> Option<(Vec,String)>; #[allow(unreachable_patterns)] fn with_local_archive( @@ -81,21 +99,31 @@ pub trait Backend { })) }) } + + /*fn get_archive_for_path(p:&Path) -> Option<(ArchiveURI,String)> { + + }*/ + #[inline] fn as_checker(&self) -> AsChecker where Self:Sized { AsChecker(self) } - fn get_html_body(&self, - d:&DocumentURI,full:bool - ) -> Option<(Vec,String)>; } impl Backend for AnyBackend { + type ArchiveIter<'a> = std::slice::Iter<'a,Archive>; #[inline] fn to_any(&self) -> AnyBackend { self.clone() } + fn with_archives(&self,f:impl FnOnce(Self::ArchiveIter<'_>) -> R) -> R where Self:Sized { + match self { + Self::Global(b) => b.with_archives(f), + Self::Temp(b) => b.with_archives(f), + } + } + #[inline] fn get_html_body(&self, d:&DocumentURI,full:bool @@ -122,13 +150,6 @@ impl Backend for AnyBackend { } } - fn with_archive_tree(&self,f:impl FnOnce(&ArchiveTree) -> R) -> R where Self:Sized { - match self { - Self::Global(b) => b.with_archive_tree(f), - Self::Temp(b) => b.with_archive_tree(f), - } - } - #[inline] fn get_module(&self, uri: &ModuleURI) -> Option { match self { @@ -210,6 +231,11 @@ impl GlobalBackend { &GLOBAL } + #[inline] + pub fn with_archive_tree(&self,f:impl FnOnce(&ArchiveTree) -> R) -> R { + self.archives.with_tree(f) + } + #[cfg(feature="tokio")] pub async fn get_html_body_async(&self, d:&DocumentURI,full:bool @@ -305,6 +331,8 @@ impl GlobalBackend { } impl Backend for &'static GlobalBackend { + type ArchiveIter<'a> = std::slice::Iter<'a,Archive>; + #[inline] fn to_any(&self) -> AnyBackend { AnyBackend::Global(self) @@ -322,9 +350,8 @@ impl Backend for &'static GlobalBackend { GlobalBackend::submit_triples(self,in_doc,rel_path,iter); } - #[inline] - fn with_archive_tree(&self,f:impl FnOnce(&ArchiveTree) -> R) -> R where Self:Sized { - GlobalBackend::with_archive_tree(self, f) + fn with_archives(&self,f:impl FnOnce(Self::ArchiveIter<'_>) -> R) -> R where Self:Sized { + GlobalBackend::with_archives(self,f) } #[inline] @@ -364,11 +391,18 @@ impl Backend for &'static GlobalBackend { } impl Backend for GlobalBackend { + type ArchiveIter<'a> = std::slice::Iter<'a,Archive>; + #[inline] fn to_any(&self) -> AnyBackend { AnyBackend::Global(Self::get()) } + #[inline] + fn with_archives(&self,f:impl FnOnce(Self::ArchiveIter<'_>) -> R) -> R where Self:Sized { + self.archives.with_tree(|t| f(t.archives.iter())) + } + fn get_html_body(&self, d:&DocumentURI,full:bool ) -> Option<(Vec,String)> { @@ -385,11 +419,6 @@ impl Backend for GlobalBackend { }); } - #[inline] - fn with_archive_tree(&self,f:impl FnOnce(&ArchiveTree) -> R) -> R { - self.archives.with_tree(f) - } - #[inline] fn with_archive(&self, id: &ArchiveId, f: impl FnOnce(Option<&Archive>) -> R) -> R { @@ -485,6 +514,8 @@ impl TemporaryBackend { } impl Backend for TemporaryBackend { + type ArchiveIter<'a> = std::slice::Iter<'a,Archive>; + #[inline] fn to_any(&self) -> AnyBackend { AnyBackend::Temp(self.clone()) @@ -495,6 +526,11 @@ impl Backend for TemporaryBackend { ) } + #[inline] + fn with_archives(&self,f:impl FnOnce(Self::ArchiveIter<'_>) -> R) -> R where Self:Sized { + self.inner.parent.with_archives(f) + } + fn get_html_body(&self, d:&DocumentURI,full:bool ) -> Option<(Vec,String)> { @@ -535,11 +571,6 @@ impl Backend for TemporaryBackend { self.inner.parent.with_archive_or_group(id, f) } - #[inline] - fn with_archive_tree(&self,f:impl FnOnce(&ArchiveTree) -> R) -> R { - self.inner.parent.with_archive_tree(f) - } - #[inline] fn with_archive(&self,id:&ArchiveId,f:impl FnOnce(Option<&Archive>) -> R) -> R { self.inner.parent.with_archive(id,f) diff --git a/source/ts/vscode/src/ts/commands.ts b/source/ts/vscode/src/ts/commands.ts index 9463e95..da4cc1f 100644 --- a/source/ts/vscode/src/ts/commands.ts +++ b/source/ts/vscode/src/ts/commands.ts @@ -30,12 +30,12 @@ export function register_server_commands(context:IMMTContext) { vscode.commands.executeCommand('setContext', 'immt.loaded', true); vscode.window.registerWebviewViewProvider("immt-tools", webview(context,"stex-tools",msg => { + const doc = vscode.window.activeTextEditor?.document; switch (msg.command) { case "dashboard": openIframe(context.server.url + "/dashboard","Dashboard"); break; case "preview": - const doc = vscode.window.activeTextEditor?.document; if (doc) { context.client.sendRequest("immt/htmlRequest",{uri:doc.uri.toString()}).then(s => { if (s) {openIframe(context.server.url + "?uri=" + encodeURIComponent(s),s); } @@ -44,6 +44,20 @@ export function register_server_commands(context:IMMTContext) { } }); } + break; + case "browser": + if (doc) { + context.client.sendRequest("immt/htmlRequest",{uri:doc.uri.toString()}).then(s => { + if (s) { + const uri = vscode.Uri.parse(context.server.url).with({query:"uri=" + encodeURIComponent(s)}); + vscode.env.openExternal(uri); + } + else { + vscode.window.showInformationMessage("No preview available; building possibly failed"); + } + }); + } + break; } }) ); diff --git a/source/ts/vscode/src/ts/setup.ts b/source/ts/vscode/src/ts/setup.ts index c62cf07..30ba4ef 100644 --- a/source/ts/vscode/src/ts/setup.ts +++ b/source/ts/vscode/src/ts/setup.ts @@ -153,7 +153,7 @@ async function download_from_github(dir:string,context:IMMTPreContext) { const dl = await download(url,zipfile); if (!dl) { return; } progress.report({ message: `Unzipping ${zipfile}` }); - const zip = await unzip(zipfile,dir,[],["settings.toml"]); + const zip = await unzip(zipfile,dir,[],["settings.toml"],["immt"],progress); if (!zip) { return; } progress.report({ message: `Removing ${zipfile}` }); fs.unlink(zipfile,err => {}); diff --git a/source/ts/vscode/src/ts/utils.ts b/source/ts/vscode/src/ts/utils.ts index 6b9e07c..991b13e 100644 --- a/source/ts/vscode/src/ts/utils.ts +++ b/source/ts/vscode/src/ts/utils.ts @@ -51,7 +51,7 @@ export async function download(url:string,to:string): Promise { }); } -export async function unzip(zip:string,to:string,files:string[],skip:string[],progress?:vscode.Progress<{ +export async function unzip(zip:string,to:string,files:string[],skip:string[],executables:string[],progress?:vscode.Progress<{ message?: string; increment?: number; }>): Promise { @@ -83,12 +83,8 @@ export async function unzip(zip:string,to:string,files:string[],skip:string[],pr const writer = fs.createWriteStream(target); readStream.on('end', () => { writer.close(); - if (!process.platform.startsWith("win")) { - const mode = entry.externalFileAttributes >>> 16; - const is_executable = (mode & 0o111) || (mode === 0o654); - if (is_executable) { - fs.chmod(target, 0o755,() => {}); - } + if (!process.platform.startsWith("win") && executables.includes(filename)) { + fs.chmod(target, 0o755,() => {}); } zipfile.readEntry(); }); diff --git a/source/utils/src/time.rs b/source/utils/src/time.rs index 87e4f72..387bbce 100644 --- a/source/utils/src/time.rs +++ b/source/utils/src/time.rs @@ -18,7 +18,7 @@ pub fn measure R, R>(f: F) -> (R, Delta) { #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Timestamp(NonZeroU64); +pub struct Timestamp(pub NonZeroU64); trait AsU64 { fn into_u64(self) -> u64;